2019 ICPC 上海

2019 ICPC 上海重現


B.Prefix Code

題意:

給n個長度不超過10的數字串,問是否存在一個串是另一個串的前綴,如果不存在則輸出Yes,如果存在就輸出No
數據範圍:n<=1e4

思路:

字典樹。

code:

#include<bits/stdc++.h>
using namespace std;
const int maxm=1e4+5;
struct Node{
    int a[maxm*10][10],tot;
    int cnt[maxm*10];//記錄node被經過次數
    int is[maxm*10];//記錄串結尾
    void init(){
        for(int i=0;i<=tot;i++){
            memset(a[i],0,sizeof a[i]);
            cnt[i]=is[i]=0;
        }
        tot=0;
    }
    void add(char *s){
        int len=strlen(s);
        int node=0;
        for(int i=0;i<len;i++){
            int v=s[i]-'0';
            if(!a[node][v])a[node][v]=++tot;
            node=a[node][v];
            cnt[node]++;
        }
        is[node]=1;
    }
    int ask(){
        for(int i=1;i<=tot;i++){
            if(is[i]&&cnt[i]>1)return 1;
        }
        return 0;
    }
}t;
char s[15];
signed main(){
    int T;
    scanf("%d",&T);
    for(int cas=1;cas<=T;cas++){
        t.init();
        int n;
        scanf("%d",&n);
        for(int i=1;i<=n;i++){
            scanf("%s",s);
            t.add(s);
        }
        printf("Case #%d: ",cas);
        if(!t.ask())puts("Yes");
        else puts("No");
    }
    return 0;
}

D.Spanning Tree Removal

題意:

給n,表示有一個n個頂點的完全圖
每次操作要從中刪除一個生成樹,問做多刪除幾次
且輸出邊的刪除方案
數據範圍:n<=1e3

思路:

因爲完全圖一共n(n-1)/2條邊,一顆生成樹n-1條邊,容易想到可以刪除n/2次
這題的難點主要在於如何構造刪邊方案。

做法:
在這裏插入圖片描述
把n個點圍成一個圓,然後按Z字形刪,這樣刪的話圓周的邊每次只減少兩條,一共n/2次減完
找一下每次刪除的點的規律:
假設起始點爲x,則第一次刪的是x-(x+1),第二次刪的是(x+1)-(x-2),第三次是(x-2)-(x+3)…
發現就是當前點先順時針移動1格,然後逆時針移動兩格,然後順時針移動三格…代碼裏面有體現

code:

#include<bits/stdc++.h>
using namespace std;
signed main(){
    int T;
    scanf("%d",&T);
    int cas=1;
    while(T--){
        int n;
        scanf("%d",&n);
        printf("Case #%d: %d\n",cas++,n/2);
        for(int i=1;i<=n/2;i++){
            int now=i;
            int nt;
            for(int j=1;j<=n-1;j++){
                if(j&1){
                    nt=(now+j)%n;
                }else{
                    nt=(now-j+n)%n;
                }
                if(!nt)nt=n;
                printf("%d %d\n",now,nt);
                now=nt;
            }
        }
    }
    return 0;
}

E.Cave Escape

題意:

大意:
給n和m表示有一個n*m的矩陣
給起點(stx,sty)和終點(edx,edy)
每個點(x,y)有權值v(x,y),
從每個點(i,j)可以走到相鄰的點(x,y),如果是第一次走到,那麼總得分會增加兩點的權值積v(i,j)*v(x,y)
現在要從起點走到終點,問路徑上最大得分是多少,注意到達終點之後可以繼續走賺得分,然後再回來
數據範圍:T組數據(T<=10),n,m<=1e3,0<=點權<100

思路:

因爲點權非負,所以走過所有的點才能得到最大得分,因此起點和終點其實是沒用的。
因爲每個點只有第一次到的時候纔會有得分,相鄰格子之間建邊,很容易看出來答案其實就是最大生成樹

但是總點數1e6,邊大概2e6,排序大約4e7,而且是多組數據,有點難頂,很多人都是1700ms左右卡過去的(時限2s)

正解是從數據範圍入手,因爲點權最大100,那麼邊權最大1e4,用類似桶的方法來存邊,避免排序
具體做法是用vector+pair開的g(x)存邊權爲x的邊,因爲邊權最大1e4,因此開1e4大小就行了,從大到小遍歷,不需要排序
最大生成樹就是常規做法,關鍵在於用桶存邊減少複雜度

總結:當需要給邊排序且邊權較小時,可以用桶來存邊避免排序

code:

#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int maxm=1e6+5;
vector<pair<int,int> >g[10000+5];
int pre[maxm];
int x[maxm];
int n,m,stx,sty,edx,edy;
int A,B,C,P;
int ffind(int x){
    return pre[x]==x?x:pre[x]=ffind(pre[x]);
}
int id(int i,int j){
    return (i-1)*m+j;
}
signed main(){
    int T;
    scanf("%d",&T);
    for(int cas=1;cas<=T;cas++){
        scanf("%d%d%d%d%d%d",&n,&m,&stx,&sty,&edx,&edy);
        scanf("%d%d%d%d%d%d",&x[1],&x[2],&A,&B,&C,&P);
        printf("Case #%d: ",cas);
        for(int i=3;i<=n*m;i++){
            x[i]=(A*x[i-1]+B*x[i-2]+C)%P;
        }
        for(int i=0;i<=10000;i++)g[i].clear();
        for(int i=1;i<=n;i++){
            for(int j=1;j<=m;j++){
                if(i+1<=n){
                    int a=id(i,j);
                    int b=id(i+1,j);
                    g[x[a]*x[b]].push_back({a,b});
                }
                if(j+1<=m){
                    int a=id(i,j);
                    int b=id(i,j+1);
                    g[x[a]*x[b]].push_back({a,b});
                }
            }
        }
        for(int i=1;i<=n*m;i++)pre[i]=i;
        int tot=0;
        ll ans=0;
        for(int i=10000;i>=0;i--){
            for(auto v:g[i]){
                int x=ffind(v.first);
                int y=ffind(v.second);
                if(x!=y){
                    ans+=i;
                    pre[x]=y;
                    tot++;
                }
            }
            if(tot==n*m-1)break;
        }
        printf("%lld\n",ans);
    }
    return 0;
}

F.A Simple Problem On A Tree

題意:

給一棵n個節點的樹,有q次操作:
操作有4種:
1.將u,v路徑上的點權改爲w
2.將u,v路徑上的點權加上w
3.將u,v路徑上的點權乘上w
4.查詢u,v路徑上的點權立方和

思路:

顯然樹剖,線段樹維護:和,平方和,立方和,乘法標記和加法標記。
在這裏插入圖片描述


H.Tree Partition

題意:

給一顆n個頂點的樹,和一個整數k
每個點有點權
要求切斷k-1條邊,將樹分成k個連通塊
每個連通塊的權值爲連通塊內的點的點權和
問最優切割下,最大連通塊的權值的最小值是多少

數據範圍:n<=1e5,k<=n

思路:

最大值最小化要往二分的方向想
顯然最大連通塊的權值滿足單調性
因此二分枚舉最大連通塊的權值,判斷能否成立即可

如何判斷權值mid能否成立:
對於某個幾點,先將所有子節點所在連通塊全部選中,如果總和大於mid,則必須切割
給子節點連通塊從大到小排序,貪心地優先切割權值大的子連通塊,切割的時候記錄切割次數
如果切割總次數小於等於k-1則滿足條件

詳見代碼

code:

#include<bits/stdc++.h>
using namespace std;
#define int long long
const int maxm=1e5+5;
int head[maxm],nt[maxm<<1],to[maxm<<1],tot;
int a[maxm];
int n,k;
int mid;
int kk;
void init(int n){
    for(int i=1;i<=n;i++)head[i]=0;
    tot=0;
}
void add(int x,int y){
    tot++;nt[tot]=head[x];head[x]=tot;to[tot]=y;
}
bool cmp(int a,int b){
    return a>b;
}
int dfs(int x,int fa){
    int sum=a[x];
    vector<int>temp;
    for(int i=head[x];i;i=nt[i]){
        int v=to[i];
        if(v==fa)continue;
        int t=dfs(v,x);
        temp.push_back(t);
        sum+=t;
    }
    if(sum<=mid)return sum;
    sort(temp.begin(),temp.end(),cmp);
    for(int v:temp){//貪心的從大到小切割
        sum-=v;
        kk++;
        if(sum<=mid)break;
    }
    return sum;
}
bool check(){
    kk=0;
    dfs(1,-1);
    return kk<=k-1;
}
signed main(){
    int T;
    cin>>T;
    signed cas=1;
    while(T--){
        cin>>n>>k;
        init(n);
        for(int i=1;i<n;i++){
            int a,b;
            cin>>a>>b;
            add(a,b);
            add(b,a);
        }
        int sum=0;
        int ma=0;
        for(int i=1;i<=n;i++){
            cin>>a[i];
            sum+=a[i];
            ma=max(ma,a[i]);
        }
        int ans=0;
        int l=ma,r=sum;
        while(l<=r){
            mid=(l+r)/2;
            if(check())ans=mid,r=mid-1;
            else l=mid+1;
        }
        printf("Case #%d: ",cas++);
        cout<<ans<<endl;
    }
    return 0;
}

K.Color Graph

題意:

給一個n個點m條邊的簡單圖,簡單圖的定義是無向圖,沒有自環和重邊。
現在你要選出來一些邊,滿足選出來的邊不構成奇環。
問最多可以選擇多少條邊。
數據範圍:n<=16,m<=n*(n-1)/2

思路:

無奇環的圖是二分圖,二進制枚舉點集的左半部,利用左半部推出右半部,對邊數取max就是答案。
因爲最優解一定是二分圖,所以這樣枚舉一定會枚舉到答案。

code:

#include<bits/stdc++.h>
using namespace std;
const int maxm=20;
vector<int>g[maxm];
int mark[maxm];
signed main(){
    int T;
    scanf("%d",&T);
    for(int cas=1;cas<=T;cas++){
        int n,m;
        scanf("%d%d",&n,&m);
        for(int i=0;i<n;i++)g[i].clear();
        for(int i=1;i<=m;i++){
            int a,b;
            scanf("%d%d",&a,&b);
            a--,b--;//把下標改成從0開始
            g[a].push_back(b);
            g[b].push_back(a);
        }
        int ans=0;
        for(int i=0;i<(1<<n);i++){//二進制枚舉左半部
            for(int j=0;j<n;j++){//標記選中的點
                mark[j]=(i>>j&1);
            }
            int temp=0;
            for(int j=0;j<n;j++){//計算邊
                if(i>>j&1){
                    for(int v:g[j]){
                        if(!mark[v])temp++;//如果另一端沒被標記說明另一端可以作爲右半部,則邊可選
                    }
                }
            }
            ans=max(ans,temp);
        }
        printf("Case #%d: %d\n",cas,ans);
    }
    return 0;
}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章