1. 程式人生 > >2016 acm/icpc 青島網路賽 題解(hdu 5878-5889,9道題)

2016 acm/icpc 青島網路賽 題解(hdu 5878-5889,9道題)

5878.I Count Two Three(打表預處理,二分)

題目大意:有一些數可以寫成2a3b5c7d的形式,稱之為”I count two three numbers”.輸入一個數,問比他大的最小的”I count two three numbers”是什麼?

題目分析:打表預處理,發現1e9範圍內的那種數只有約6000個,都列出來,然後排個序,lower_bound二分查詢之。

在這裡順便總結下吧,lower_bound(start,end,n)upper_bound(start,end,n),還有binary_search(start,end,n)的區別如下:

2016.11.16 cmershen 修正:


1. lower_bound演算法返回一個非遞減序列[first, last)中的第一個大於等於值val的位置。
2. upper_bound演算法返回一個非遞減序列[first, last)中第一個大於val的位置。
3. binary_search返回true/false。

by the way,第三個函式應該很少會有人用吧~~

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
ll num[6000];
int t,n;
int pre() {
    int i=0;
    ll cur=1
; //2^30,3^19,5^13,7^11 > 1e9 for(int a=0;a<=30;a++) { for(int b=0;b<=19;b++) { for(int c=0;c<=13;c++) { for(int d=0;d<=11;d++) { cur=pow(2,a)*pow(3,b); if(cur>1e9) break; cur*=pow(5,c); if
(cur>1e9) break; cur*=pow(7,d); if(cur>1e9) break; num[i++]=cur; } } } } sort(num,num+i); return i; } int main() { int i=pre(); scanf("%d",&t); while(t--) { scanf("%d",&n); int p=lower_bound(num,num+i,n)-num; printf("%d\n", num[p]); } }

5879.Cure(高等數學題)

題目大意:輸入n,求
k=1n1k2,保留五位小數。

題目分析:我們在高數中學過,(啥時候學的我怎麼不記得了,捂臉)那個和收斂於
π261.64493。(證明略,本糖也不知道~)那麼就先打表列舉下,發現n取到110293的時候,前五位小數就是1.64493了,所以如果輸入的n大於110293的時候,就直接輸出1.64493,否則查表。

這裡有個坑,就是題目中沒說n多大, 所以是任意大的,要用字串讀入。

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
double f[110300];
char s[1111];
void pre() {
    f[0]=0.0;
    f[1]=1.0;
    ll i=2;
    while(i<110300) {
        f[i]=f[i-1]+1.0/(i*i);
        i++;
    }
}
int main() {
    pre();
    while(scanf("%s",s)!=EOF) {
        if(strlen(s)>=7) {
            printf("1.64493\n");
        }
        else {
            int a;
            sscanf(s,"%d",&a);
            if(a>=110293)
                printf("1.64493\n");
            else
                printf("%.5lf\n", f[a]);
        }
    }
}

5880.Family View(ac自動機)

題目大意:
給出一些“和諧單詞”,和一段文章,將和諧單詞用等長星號代替,忽略大小寫。

題目分析:
一眼就看出來是ac自動機,模板題。下面的程式碼可以當做模板用,若註釋第70行則不忽略大小寫。

/*
*@author:Dan__ge (modified by cmershen)
*@description:AC自動機模板,輸入和諧字典,和待匹配的字串,把字串都和諧成星號,去掉70行的註釋則變成大小寫不敏感!!!
*@source:hdu 5880
*/

#include <queue>
#include <stdio.h>
#include <string.h>
#include <iostream>
#include <algorithm>
using namespace std;
const int maxn=1000010;
int ans[maxn],cnt[maxn];
struct Trie{
    int next[maxn][26],fail[maxn],end[maxn];
    int root,L,ko;
    int newnode(){
        for(int i = 0;i < 26;i++)
            next[L][i] = -1;
        end[L++] = 0;
        return L-1;
    }
    void init(){
        L = 0,ko=0;
        root = newnode();
        memset(ans,0,sizeof(ans));
    }
    void insert(char buf[]){
        int len = strlen(buf);
        int now = root;
        for(int i = 0;i < len;i++){
            if(next[now][buf[i]-'a'] == -1)
                next[now][buf[i]-'a'] = newnode();
            now = next[now][buf[i]-'a'];
        }
        if(end[now]==0){
            ko++;end[now]=ko;
            cnt[ko]=len;
        }else{
            if(cnt[end[now]]<len) cnt[end[now]]=len;
        }
    }
    void build(){
        queue<int>Q;
        fail[root] = root;
        for(int i = 0;i < 26;i++)
            if(next[root][i] == -1) next[root][i] = root;
            else{
                fail[next[root][i]] = root;
                Q.push(next[root][i]);
            }
        while( !Q.empty() ){
            int now = Q.front();
            Q.pop();
            for(int i = 0;i < 26;i++)
                if(next[now][i] == -1) next[now][i] = next[fail[now]][i];
                else{
                    fail[next[now][i]]=next[fail[now]][i];
                    Q.push(next[now][i]);
                }
        }
    }
    void query(char buf[]){
        int len = strlen(buf);
        int now = root;
        int res = 0;
        for(int i = 0;i < len;i++){
            if(buf[i]>='a'&&buf[i]<='z') now = next[now][buf[i]-'a'];
            else if(buf[i]>='A'&&buf[i]<='Z') now = next[now][buf[i]-'A'];
            else continue;
            int temp = now;
            while( temp != root ){
                ans[i+1]--;ans[i-cnt[end[temp]]+1]++;
                temp = fail[temp];
            }
        }
    }
};
char buf[maxn];
Trie ac;
int main(){
    int T,n;
    scanf("%d",&T);
    while( T-- ){
        scanf("%d",&n);
        ac.init();
        for(int i = 0;i < n;i++){
            scanf("%s",buf);
            ac.insert(buf);
        }
        ac.build();
        getchar();
        gets(buf);
        ac.query(buf);
        int len=strlen(buf),sum=0;
        for(int i=0;i<len;i++){
            sum+=ans[i];
            if(sum<=0) printf("%c",buf[i]);
            else printf("*");
        }
        printf("\n");
    }
    return 0;
}

5881.Tea(貪心)

題目大意:給你一壺茶,你不知道茶有多少,只知道在[L,R]之間,給你兩個杯子,要求兩個杯子的茶水量差不超過1,杯子最後剩餘的水不超過1,問至少倒幾次。

題目分析:這題我有點不太理解,就直接貼下答案吧,因為他說至少倒幾次是在[L,R]的最壞條件下還是最好條件下?如果考慮最好條件下,那麼R值就沒用了,倒兩次就好了。還有就是你是不是隻知道茶水什麼時候剩1以下?

這道題的解釋是這樣的:
* 如果R1,那麼不用倒直接滿足題意。
* 如果R=2,那就倒1次,這裡我有個問題,就是如果是[0,2]這個輸入,那麼你是有可能倒不出來的啊。。。。。。。
* 如果RL1,那麼就倒兩次,每次倒L/2,這樣剩的肯定不超過1.
* 否則,先倒兩次,分別是(L1)/2,(L+1)/2,再一邊倒2.

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
ll l,r;
int main() {
    while(scanf("%I64d %I64d",&l,&r)!=EOF) {
        ll ans;
        if(r<=1)
            ans=0;
        else if(r<=2)
            ans=1;
        else if(r-l<=1)
            ans=2;
        else {
            if(l<=1) l=1;
            ans=(r-l)/2+1;
        }
        printf("%I64d\n", ans);
    }
}

5882.Balanced Game(水題)

題目大意:
問n個手勢的石頭剪子布是否公平?

題目分析:
說實話這題我也沒徹底理解,因為從直覺上看,就是奇數公平偶數不公平,而且偶數不公平顯然如此。

but,如何證明奇數一定公平?有沒有一種對任意n均公平的分配方案?這個有待進一步探討啊~~
2016.11.16 cmershen add
知乎上有大神回覆了,可以這樣設計方案:
在圓周上等距離分佈2n+1個點,對任意一個點,順時針數n個點贏他,逆時針數n個點輸他,這樣就可以保證任意兩種不同手勢均能分出勝負,且完全公平。

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
int t,n;
int main() {
    scanf("%d",&t);
    while (t--) {
        scanf("%d",&n);
        if (n&1)
            printf("Balanced\n");
        else
            printf("Bad\n");
    }
}

5883.The Best Path(歐拉回路)

題目大意:n個點,m個邊的無向圖,每個點有權值ai,問可不可以每條邊只走一遍,遍歷所有邊?如果可以,則求出路徑上經過的所有點(可以重複)異或的最大值,如果不可以,輸出Impossible

題目分析:
求尤拉路咯,小學奧數都學過,看有幾個度為奇數的點。如果是0個,則是歐拉回路,如果是2個,則是尤拉通路。

接下來就看每個點要經過幾次:

如果度為n,那麼這n條邊都要遍歷一次,而每走其中兩條邊,就要經過這個點一次(因為要來到這個點,還得出去),如果剩下一條邊,那麼也要走一次這個點,因為它必是起點/終點。

根據異或的性質,如果這個次數值是奇數,那就加到答案裡,如果是偶數,那就消掉了。

那麼如果該圖存在的是尤拉通路,那答案就是唯一的,如果是歐拉回路,相當於起點多走了一次,列舉起點即可。

這題的資料我覺得有點弱,因為還需要考慮整個圖是否連通的問題,但是沒考慮也過了。

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
int t,n,m,u,v;
short a[100005];
int deg[100005];
vector<int> g[100005];
int main() {
    scanf("%d",&t);
    while (t--) {
        scanf("%d %d",&n,&m);
        for(int i=1;i<=n;i++)
            g[i].clear();
        memset(deg,0,sizeof(deg));
        for(int i=1;i<=n;i++)
            scanf("%d",&a[i]);
        for(int i=0;i<m;i++) {
            scanf("%d %d",&u,&v);
            g[u].push_back(v);
            g[v].push_back(u);
            deg[u]++;deg[v]++;
        }
        int cnt=0,sum=0;
        for(int i=1;i<=n;i++) {
            if(deg[i]&1)
                cnt++;
            int temp=(deg[i]+1)/2;
            if(temp & 1)
                sum^=a[i];
        }
        if(cnt==0) { //Eular Curcuit
            int ans=0;
            for(int i=1;i<=n;i++)
                ans=max(ans,sum^a[i]);
            printf("%d\n", ans);
        }
        else if(cnt==2)  //Eular Path
            printf("%d\n", sum);
        else
            printf("Impossible\n");
    }
}

5884. sort(huffman樹)

題目大意:
給你n個正整數和最大花費T,每次可以合併不超過k個數,花費是這k個數的和,最後合併成一個數,求不超過花費T的情況下,最小的k。

題目分析:
其實這就是一個k叉Huffman樹求帶權路徑長度的問題。首先考慮到我們要用k-1次操作合併n-1個數,那麼當
(k1)%(n1)!=0時,最後剩下的數就會不到k個,所以我們先將多出來的部分合併成一個,顯然先合併要比後合併花費小。
接下來就是求這個最小的k,因為k的取值範圍確定為 [2,n],且花費cost(k) 對k單調遞減(這個我不知道咋證。。。直覺如此),因此在 [2,n] 上二分答案就ok了。

如果你就這麼做下去,那麼你肯定tle了。

這題卡了logn(大概在6左右),直接用優先佇列的複雜度是O(lognnlogn) ,因此要優化一下。
用兩個佇列q1和q2,其中q1裝原來的數(排序好),q2裝合併後的數,根據Huffman樹性質,合併數肯定是越合越大,所以q2也是單調的。那麼每次先從q1和q2里加一起取k個數,合併起來扔q2裡,直到q1取完,再取q2,直到q2裡剩一個數。這樣做就能過了,時間在1s左右。

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
int t0,t,n;
int a[100005];
int cost(int x) {
    queue<int> q1,q2;
    int ans=0;
    sort(a,a+n);
    for(int i=0;i<n;i++)
        q1.push(a[i]);
    if((n-1)%(x-1)!=0) {
        int temp=(n-1)%(x-1)+1;
        int sum=0;
        for(int j=0;j<temp;j++) {
            sum+=q1.front();
            q1.pop();
        }
        q2.push(sum);
        ans+=sum;
    }
    while(!q1.empty()) {
        int sum=0;
        for(int j=0;j<x;j++) {
            if(!q1.empty() && !q2.empty()) {
                if(q1.front()<q2.front()) {
                    sum+=q1.front();
                    q1.pop();
                }
                else {
                    sum+=q2.front();
                    q2.pop();
                }
            }
            else if(q1.empty()) {
                sum+=q2.front();
                q2.pop();
            }
            else if(q2.empty()) {
                sum+=q1.front();
                q1.pop();
            }
        }
        ans+=sum;
        q2.push(sum);
    }
    while(q2.size()>1) {
        int sum=0;
        for(int i=0;i<x;i++) {
            ans+=q2.front();
            q2.pop();
        }
        ans+=sum;
        q2.push(ans);
    }
    return ans;
}
int main() {
    scanf("%d",&t0);
    while(t0--) {
        scanf("%d %d",&n,&t);
        for(int i=0;i<n;i++) {
            scanf("%d",&a[i]);
        }
        int l=2,r=n;
        while(l<r) {
            int mid=(l+r)/2;
            if(cost(mid)<=t)
                r=mid;
            else
                l=mid+1;
        }
        printf("%d\n", l);
    }
}

5887. Herbs Gathering(dfs解01揹包+剪枝)

題目大意:
01揹包,100個物品,體積和揹包容量可達1e9。

題目分析:

我們傳統的用DP解揹包都是物品多,揹包容量M有限,這樣可以用DP做,在O(nm) 內就能搞定了。

但此題的M太大了,N又很小,所以用搜索來解。但2100 的搜尋空間也不行啊,所以剪枝吧~

這道題我見到的黑科技很多,最極端的是用時間剪枝,就是一個dfs跑到10ms就return,居然也能過。。。。。。。。

我採用的是按價效比剪枝,也就是先按價效比排序,如果揹包剩下的空間裡全裝這個東西也沒有更優解,則停止搜尋(因為後面的價效比低,就更不可能找到解了),這樣就0ms AC了。

#include <bits/stdc++.h>
using namespace std;
#define RE(x) freopen(x,"r",stdin)
#define WR(x) freopen(x,"w",stdout)
typedef long long ll;
int n,t;
typedef struct  {
    int t;//time
    int s;//score
    double r;//rate
}herb;
herb h[105];
bool cmp(herb x,herb y) {
    return x.r>y.r;
}
ll ans;
void dfs(int i,ll cur,ll sc) { //決定第i個物品放不放,當前揹包容量為cur,得分為sc
    if(sc>ans)
        ans=sc;
    if(sc+h[i].r*(t-cur)<ans) return;//剩下的容積就算全放i,也得不到更優解,更何況後面的價效比更低呢
    if(i<n) {
        if(h[i].t<=t-cur) //第i個物品裝得下
            dfs(i+1,cur+h[i].t,sc+h[i].s);
        dfs(i+1,cur,sc);//不放i
    }
}
int main() {
    RE("in.txt");
    WR("out.txt");
    while(scanf("%d %d",&n,&t)!=EOF) {
        for(int i=0;i<n;i++) {
            scanf("%d %d",&h[i].t,&h[i].s);
            h[i].r=(double)(h[i].s)/(double)(h[i].t);
        }
        ans=0;
        sort(h,h+n,cmp);
        dfs(0,0,0);//決定第0個物品,目前揹包裡容量為0,得分為0
        printf("%I64d\n", ans);
    }
}

5889.Barricade(最短路徑+網路流)

題目大意:

有個地圖,n個點,m個邊,無向圖,每條邊的長度都一樣。

你在1點,敵人在n點,已知敵人一定按最短路線來找你,所以你要將敵人的路設定障礙,使得敵人不管沿什麼路徑過來,都會遇到你的障礙。又已知在第i條邊設定障礙需要cost[i]的花費,求至少多少花費才能使敵人全部遇到障礙。

題目分析:

首先在原圖上跑一遍dijkstra演算法,求1點到其他點的最短路,然後構造一張新圖,在最短路的路徑上(條件是dis[i]-dis[j]==1 && i,j有邊)加邊,邊權為設定障礙的花費。然後新圖上以1為源,n為匯點跑最大流即可。

這裡分別給出dinic演算法和SAP演算法的AC程式碼,供當做模板用,其中sap演算法用時15ms,dinic演算法用時46ms。

//sap,15ms
#include <bits/stdc++.h>
using namespace std;
#define RE(x) freopen(x,"r",stdin)
#define WR(x) freopen(x,"w",stdout)
typedef long long ll;
#define INF 0x3f3f3f3f
int t,u,v,w,n,m;
int cost[1005][1005];
int dis[1005];

struct EDGE {
    int u,v,cap;
    int next;
}edge[20005];
int head[1005],p;
int gap[1005],dep[1005],cur[1005],stk[1005];
void addedge(int u,int v) {
    int c=cost[u][v];
    edge[p].u=u;edge[p].v=v;edge[p].cap=c;
    edge[p].next=head[u]; head[u]=p++;

    edge[p].u=v;edge[p].v=u;edge[p].cap=0;
    edge[p].next=head[v]; head[v]=p++;
}
void dijkstra(int u) { //u點為源,求單源最短路徑
    bool vis[1005];
    for(int i=1;i<=n;i++) {
        if(cost[i][u]!=INF && i!=u)
            dis[i]=1;
        else
            dis[i]=INF;
        vis[i]=false;
    }
    dis[u]=0;
    vis[u]=true;
    for(int i=1;i<=n;i++) {
        int min=INF;
        int x;
        for(int j=1;j<=n;j++) {
            if(!vis[j] && dis[j]<min) {
                min=dis[j];
                x=j;
            }
        }
        vis[x]=true;
        for(int j=1;j<=n;j++)
            if(!vis[j] && cost[x][j]!=INF && min+1<dis[j])
                dis[j]=min+1;
    }
}
void bfs(int t) {
    memset(dep,-1,sizeof(dep));
    memset(gap,0,sizeof(gap));
    queue<int> q;
    dep[t]=0;
    gap[0]=1;
    q.push(t);
    while(!q.empty()) {
        int u=q.front();
        q.pop();
        for(int i=head[u];i!=-1;i=edge[i].next) {
            int v=edge[i].v;
            if(edge[i^1].cap>0 && dep[v]==-1) {
                q.push(v);
                dep[v]=dep[u]+1;
                gap[dep[v]]++;
            }
        }
    }
}
int sap(int s,int t) {
    bfs(t);
    memcpy(cur,head,sizeof(cur));
    int ans=0;
    int u=s,top=0,i;
    while(dep[s]<n) {
        if(u==t) {
            int delta=INF;
            int flag=n;
            for(i=0;i!=top;i++) {
                if(delta>edge[stk[i]].cap) {
                    delta=edge[stk[i]].cap;
                    flag=i;
                }
            }
            for(i=0;i!=top;i++) {
                edge[stk[i]].cap-=delta;
                edge[stk[i]^1].cap+=delta;
            }
            ans+=delta;
            top=flag;
            u=edge[stk[top]].u;
        }
        for(i=cur[u];i!=-1;i=edge[i].next) {
            int v=edge[i].v;
            if(edge[i].cap>0 && dep[u]==dep[v]+1)
                break;
        }
        if(i!=-1) {
            cur[u]=i;
            stk[top++]=i;
            u=edge[i].v;
        }
        else {
            if(--gap[dep[u]]==0)
                break;
            int mind=n+1;
            for(i=head[u];i!=-1;i=edge[i].next) {
                if(edge[i].cap>0 && mind>dep[edge[i].v]) {
                    mind=dep[edge[i].v];
                    cur[u]=i;
                }
            }
            dep[u]=mind+1;
            gap[dep[u]]++;
            u=(u==s)?u:edge[stk[--top]].u;
        }

    }
    return ans;
}
int main() {
    scanf("%d",&t);
    while(t--) {
        scanf("%d %d",&n,&m);
        memset(cost,0x3f,sizeof(cost));
        memset(head,-1,sizeof(head));
        p=0;
        for(int i=1;i<=m;i++) {
            scanf("%d %d %d",&u,&v,&w);
            cost[u][v]=w;
            cost[v][u]=w;
        }
        dijkstra(1);
        for(int i=1;i<=n;i++) {
            for(int j=1;j<=n;j++) {
                if(dis[i]+1==dis[j] && cost[i][j]!=INF)
                    addedge(i,j);
            }
        }
        printf("%d\n", sap(1,n));        //源點1,匯點n,求最大流
    }

}
//Dinic,46ms
#include <bits/stdc++.h>
using namespace std;
#define RE(x) freopen(x,"r",stdin)
#define WR(x) freopen(x,"w",stdout)
typedef long long ll;
#define INF 0x3f3f3f3f
int t,u,v,w,n,m;
int cost[1005][1005];
int dis[1005];

struct EDGE {
    int to,next,flow;
}edge[20005];
int head[10005],p,level[10005];
void addedge(int u,int v) {
    int w=cost[u][v];
    edge[p].to=v;
    edge[p].flow=w;
    edge[p].next=head[u];
    head[u]=p;p++;

    edge[p].to=u;
    edge[p].flow=0;
    edge[p].next=head[v];
    head[v]=p;p++;
}
void dijkstra(int u) {