1. 程式人生 > >2018-2019 ICPC, NEERC, Southern Subregional Contest (Online Mirror) 體驗記

2018-2019 ICPC, NEERC, Southern Subregional Contest (Online Mirror) 體驗記

 

原文連結https://www.cnblogs.com/zhouzhendong/p/CF1070.html

比賽網址:http://codeforces.com/contest/1070

感受

  本來只打算玩玩的。

  結果玩的海星。

  我做 A 題的時候可能比較浮躁吧,各種傻錯誤,爆了 7 個罰時。

  我 A 還沒過,cly 神仙已經把 CDK 切光了。

  4點半過一會兒,各自回家。cly 要剃頭,就咕咕咕了。我本來也要剃的,後來臨時決定先打題。

  然後過了 A ,順手切掉了 H 和 F ,開了 E ,猜它是個凸函式,果斷 三分,結果 wa 了。

  這個時候剛好 cly 咕回來了。一眼就說不是凸的,tql 。然後我仔細想想發現的確不是,但還是還是可做的,把我程式碼稍加修改就過了。

  cly 看了一波 I 說(顯然是裝的)他不大會。經過一波討論之後,發現一個網路流做法。然後 cly 把鍋給了我。

  誰知道不好的事情發生了:我一開始想的建圖是萎的。直到後來,換了建圖方式才過。在此期間,cly 幹掉了 J 和 G,tql 。

  然後一起肝 B 。這個題目好**長。也許只有 AK英語 的 cly 才能讀懂了。十幾分鍾過去,cly 告訴了我題意。

  這……這不是傻題嗎?直接建個 Trie 就沒了啊!為什麼過的人這麼少??????要來不及了!快快快!於是開始碼呀碼……

  結果我離 AC 差了 4 分鐘。

  然後就只有 rk26 啦。

  沒事反正是玩玩。

 

部分題目 做法概要 或 程式碼

A  簡單題——cly 0.1s 想出做法,估計 1s 就可以 AC 的題。 直接兩維狀態,一維是對於 d 取模的值,一維是數位和。直接 bfs 一下,然後在到達目標點的最短路 DAG 上面貪心地走就好了。注意細節。

不知道為什麼我寫的程式碼像xiang一樣。

#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
LL read(){
    LL x=0,f=1;
    char ch=getchar();
    
while (!isdigit(ch)&&ch!='-') ch=getchar(); if (ch=='-') f=-1,ch=getchar(); while (isdigit(ch)) x=(x<<1)+(x<<3)+(ch^48),ch=getchar(); return x*f; } const int D=510,S=5505; int d,s; int dis[D][S],vis[D][S]; struct Node{ int d,s; Node(){} Node(int _d,int _s){ d=_d,s=_s; } }q[D*S]; vector <int> rev[D][S]; string ans=""; int Ha(int x,int y){ return x*100000+y; } int Q[D*S]; int main(){ d=read(),s=read(); int head=0,tail=0; memset(dis,63,sizeof dis); dis[0][0]=0,vis[0][0]=1; q[++tail]=Node(0,0); while (head<tail){ Node now=q[++head]; for (int i=(s==0);i<10;i++){ int _d=(now.d*10+i)%d; int _s=now.s+i; if (_s>s) continue; if (vis[_d][_s]){ if (dis[_d][_s]==dis[now.d][now.s]+1) rev[_d][_s].push_back(Ha(now.d,now.s)); continue; } rev[_d][_s].push_back(Ha(now.d,now.s)); vis[_d][_s]=1; dis[_d][_s]=dis[now.d][now.s]+1; q[++tail]=Node(_d,_s); } } if (!vis[0][s]) return puts("-1"),0; #define tag vis memset(tag,0,sizeof tag); head=tail=0; Q[++tail]=Ha(0,s); tag[0][s]=1; while (head<tail){ int now=Q[++head]; int nowd=dis[now/100000][now%100000]; for (auto y : rev[now/100000][now%100000]){ if (tag[y/100000][y%100000]||dis[y/100000][y%100000]!=nowd-1) continue; tag[y/100000][y%100000]=1; Q[++tail]=y; } } Node now=Node(0,0); while (now.d!=0||now.s!=s){ for (int i=(now.s==0);i<10;i++){ int _d=(now.d*10+i)%d; int _s=now.s+i; if (_s>s) continue; if (tag[_d][_s]&&dis[_d][_s]==dis[now.d][now.s]+1){ ans+='0'+i; now=Node(_d,_s); break; } } } cout << ans; return 0; }
CF1070A

 

B  過的人少大概是因為題意毒瘤和讀入格式毒瘤吧。直接來個 Trie ,然後 dfs 一遍就做完了。不過要注意細節。

#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
LL read(){
    LL x=0,f=1;
    char ch=getchar();
    while (!isdigit(ch)&&ch!='-')
        ch=getchar();
    if (ch=='-')
        f=-1,ch=getchar();
    while (isdigit(ch))
        x=(x<<1)+(x<<3)+(ch^48),ch=getchar();
    return x*f;
}
const int N=200005;
int n;
int Next[N*34][2],c[N*34],tot=1;
LL res[N];
int kr[N],cnt=0;
void Add(int t,LL v,int k){
    int x=1;
    for (int i=31;i>=k;i--){
        int y=v>>i&1;
        if (!Next[x][y])
            Next[x][y]=++tot;
        x=Next[x][y];
    }
    c[x]|=1<<t;
}
int dfs(int x){
    if (!x)
        return 1;
    int ls=Next[x][0],rs=Next[x][1];
    if (!dfs(ls)||!dfs(rs))
        return 0;
    int cc=c[ls]|c[rs];
    if (c[x]==3||(c[x]==1&&(cc&2))||(c[x]==2&&(cc&1)))
        return 0;
    c[x]|=c[ls]|c[rs];
}
void Out(LL v,int k,int f){
    printf("%d.%d.%d.%d/%d",
    (int)((v>>24)&255),
    (int)((v>>16)&255),
    (int)((v>>8)&255),
    (int)((v>>0)&255),k);
    if (f)
        puts("");
}
void solve(int x,LL v,int d){
    if (!x||!c[x])
        return;
    if (c[x]==3){
        solve(Next[x][0],v,d-1);
        solve(Next[x][1],v|(1<<(d-1)),d-1);
        return;
    }
    if (c[x]==1){
        cnt++;
        res[cnt]=v;
        kr[cnt]=32-d;
    }
}
int main(){
    n=read();
    while (n--){
        char s[1000];
        scanf("%s",&s);
        char *p=s;
        int t=(*p)!='-';
        s[strlen(s)]='#';
        LL v=0,k=32;
        for (int i=0;i<4;i++){
            p++;
            int x=0;
            while (isdigit(*p))
                x=(x<<1)+(x<<3)+((*p)^48),p++;
            v=(v<<8)|x;
        }
        if ((*p)=='/'){
            p++;
            int x=0;
            while (isdigit(*p))
                x=(x<<1)+(x<<3)+((*p)^48),p++;
            k=x;
        }
        Add(t,v,32-k);
    }
    if (!dfs(1))
        return puts("-1"),0;
    solve(1,0,32);
    cout << cnt << endl;
    for (int i=1;i<=cnt;i++)
        Out(res[i],kr[i],i<cnt);
    return 0;
}
CF1070B

 

C  這題是 cly 做的,我只口胡了一下。大概就是把詢問的第k大轉成在值域上二分。樹狀陣列維護一下直接在樹狀陣列上二分就好了。

這裡拖陳老爺程式碼

#include <bits/stdc++.h>
using namespace std;
const int N = 1000010;
typedef pair<int,int> pii;
typedef long long ll;
int n,k,m,mx;
ll ans;
vector<pii> vec[N];
#define lowbit(x) ((x) & (-(x)))
ll num[N],val[N];
void add(ll* v,int p,ll val) {
  for ( ; p <= mx ; p += lowbit(p))
    v[p] += val;
}
ll ask(ll* v,int p) {
  ll ret = 0;
  for ( ; p ; p -= lowbit(p))
    ret += v[p];
  return ret;
}
int query() {
  int t = k, ret = 0;
  for (int i = 20 ; i >= 0 ; -- i) {
    if (ret + (1 << i) <= mx) {
      if (t > num[ret + (1 << i)]) {
    t -= num[ret + (1 << i)];
    ret += 1 << i;
      }
    }
  }
  return ret;
}
int main() {
  int a,b,c,d;
  scanf("%d%d%d",&n,&k,&m);
  for (int i = 1 ; i <= m ; ++ i) {
    scanf("%d%d%d%d",&a,&b,&c,&d);
    vec[a].push_back(pii(c,d));
    vec[b+1].push_back(pii(-c,d));
    mx = max(d,mx);
  }
  for (int i = 1 ; i <= n ; ++ i) {
    for (int j = 0 ; j < (int)vec[i].size() ; ++ j) {
      add(num,vec[i][j].second,vec[i][j].first);
      add(val,vec[i][j].second,1ll * vec[i][j].first * vec[i][j].second);
    }
    int tmp = query();
    ans += ask(val,tmp);
    if (tmp < mx) ans += 1ll * (tmp + 1) * (k - ask(num,tmp));
  }
  cout << ans << endl;
  return 0;
}
CF1070C

 

D  略

E  設 f(x) 表示 d=x 時,能得到的答案。很容易發現這個函式很像一個凸函式。在思索一番發現這個函式從 1 開始是遞增的,到達一個最高點之後,永遠都不會超過這個最高點。所以直接找到這個最高點,然後小範圍暴力算一算(防wa)就好了。

#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
LL read(){
    LL x=0,f=1;
    char ch=getchar();
    while (!isdigit(ch)&&ch!='-')
        ch=getchar();
    if (ch=='-')
        f=-1,ch=getchar();
    while (isdigit(ch))
        x=(x<<1)+(x<<3)+(ch^48),ch=getchar();
    return x*f;
}
const int N=200005;
int T,n,m;
LL t;
int p[N];
int f(int d){
    int cnt=0,ans=0;
    LL Time=0,nowT=0;
    for (int i=1;i<=n&&Time<=t;i++){
        if (p[i]>d)
            continue;
        if (Time+p[i]>t)
            break;
        cnt++,ans++;
        nowT+=p[i];
        Time+=p[i];
        if (cnt==m)
            cnt=0,Time+=nowT,nowT=0;
    }
    return ans;
}
int fck(int d){
    int cnt=0,ans=0;
    LL Time=0,nowT=0;
    int i;
    for (i=1;i<=n&&Time<=t;i++){
        if (p[i]>d)
            continue;
        if (Time+p[i]>t)
            return 0;
        cnt++,ans++;
        nowT+=p[i];
        Time+=p[i];
        if (cnt==m)
            cnt=0,Time+=nowT,nowT=0;
    }
    return i>n;
}
void Main(){
    n=read(),m=read(),t=read();
    for (int i=1;i<=n;i++)
        p[i]=read();
    int k=0;
    for (int i=19;i>=0;i--)
        if (fck(k+(1<<i)))
            k|=1<<i;
    int ans=0,id=1;
    for (int i=k;i<=k+10;i++){
        int v=f(i);
        if (v>ans)
            ans=v,id=i;
    }
    cout << ans << " " << min((LL)id,t)<< endl;
}
int main(){
    T=read();
    while (T--)
        Main();
    return 0;
}
CF1070E

 

F  11 的肯定全部取。然後我們取價值總和儘量大的若干對  10 和 01 ,最終 10 或者 01 會有剩餘,這剩下的人的本質(是復讀機)就和 00 一樣了,直接和 00 一起考慮。現在我們還能取一些人,算出來可以取多少個,把 00 的人的價值按照大到小排序取前幾個就好了。

#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
LL read(){
    LL x=0,f=1;
    char ch=getchar();
    while (!isdigit(ch)&&ch!='-')
        ch=getchar();
    if (ch=='-')
        f=-1,ch=getchar();
    while (isdigit(ch))
        x=(x<<1)+(x<<3)+(ch^48),ch=getchar();
    return x*f;
}
const int N=400005;
int n;
vector <int> v[4];
int main(){
    n=read();
    for (int i=1;i<=n;i++){
        int a=read(),b=read();
        a=(a/10)<<1|(a%10);
        v[a].push_back(b);
    }
    for (int i=0;i<4;i++){
        sort(v[i].begin(),v[i].end());
        reverse(v[i].begin(),v[i].end());
    }
    int ans=0,m=v[3].size();
    for (auto y : v[3])
        ans+=y;
    if (v[1].size()>v[2].size())
        swap(v[1],v[2]);
    int mi=m+v[1].size();
    m+=v[1].size()*2;
    for (int i=0;i<v[1].size();i++)
        ans+=v[1][i]+v[2][i];
    for (int i=v[1].size();i<v[2].size();i++)
        v[0].push_back(v[2][i]);
    sort(v[0].begin(),v[0].end());
    reverse(v[0].begin(),v[0].end());
    int re=mi*2-m;
    for (int i=0;i<min(re,(int)v[0].size());i++)
        ans+=v[0][i];
    cout << ans;
    return 0;
}
CF1070F

 

H  直接把每一個串的每一個子串資訊壓縮成 long long,扔到 map 裡面就好了。

#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
LL read(){
    LL x=0,f=1;
    char ch=getchar();
    while (!isdigit(ch)&&ch!='-')
        ch=getchar();
    if (ch=='-')
        f=-1,ch=getchar();
    while (isdigit(ch))
        x=(x<<1)+(x<<3)+(ch^48),ch=getchar();
    return x*f;
}
const int N=10005;
int n;
char s[N][10];
map <LL,int> Map;
int tot[N*100],id[N*100],cnt=0;
LL Ha(char *s,int n){
    LL res=0;
    for (int i=0;i<n;i++)
        res=(res<<7)|s[i];
    return res;
}
int main(){
    n=read();
    Map.clear();
    for (int x=1;x<=n;x++){
        LL c[1000];
        int ct=0;
        scanf("%s",s[x]);
        int m=strlen(s[x]);
        for (int i=0;i<m;i++)
            for (int j=i;j<m;j++)
                c[++ct]=Ha(s[x]+i,j-i+1);
        sort(c+1,c+ct+1);
        ct=unique(c+1,c+ct+1)-c-1;
        for (int i=1;i<=ct;i++){
            LL v=c[i];
            if (!Map[v]){
                Map[v]=++cnt;
                id[cnt]=x;
            }
            tot[Map[v]]++;
        }
    }
    int q=read();
    while (q--){
        char ss[10];
        scanf("%s",ss);
        LL now=Ha(ss,strlen(ss));
        int k=Map[now];
        int ans=tot[k];
        cout << ans << " ";
        if (ans)
            cout << s[id[k]] << endl;
        else
            cout << "-\n";
    }
    return 0;
}
CF1070H

 

I  好像做了這麼多就這道非常有趣。首先我們很容易對於每一個點,算出連線它的邊的顏色中,只出現一次的顏色個數。考慮每條邊最多隻會對於它所連線的其中一端貢獻到“顏色出現兩次的邊”中,所以我們直接根據這些限制用網路流分配一下每一條邊在哪一端是“顏色只出現一次的邊”即可。(怎麼又一句話說完了……)

#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
LL read(){
    LL x=0,f=1;
    char ch=getchar();
    while (!isdigit(ch)&&ch!='-')
        ch=getchar();
    if (ch=='-')
        f=-1,ch=getchar();
    while (isdigit(ch))
        x=(x<<1)+(x<<3)+(ch^48),ch=getchar();
    return x*f;
}
struct Edge{
    int x,y,cap,nxt;
    Edge(){}
    Edge(int a,int b,int c,int d){
        x=a,y=b,cap=c,nxt=d;
    }
};
struct gragh{
    static const int N=605*4,M=1000000,INF=0x7FFFFFFF;
    int n,S,T,fst[N],cnt;
    int dist[N],num[N],cur[N],p[N];
    LL MaxFlow;
    Edge e[M];
    void clear(int _n){
        cnt=1,n=_n;
        memset(fst,0,sizeof fst);
    }
    void add(int a,int b,int c){
        e[++cnt]=Edge(a,b,c,fst[a]),fst[a]=cnt;
        e[++cnt]=Edge(b,a,0,fst[b]),fst[b]=cnt;
    }
    void init(){
        memset(dist,0,sizeof dist);
        memset(num,0,sizeof num);
        for (int i=1;i<=n;i++)
            num[dist[i]]++,cur[i]=fst[i];
    }
    void init(int _S,int _T){
        S=_S,T=_T;
        MaxFlow=0;
        init();
    }
    int Augment(int &x){
        int Flow=INF;
        for (int i=T;i!=S;i=e[p[i]].x)
            if (e[p[i]].cap<=Flow)
                Flow=e[p[i]].cap,x=e[p[i]].x;
        for (int i=T;i!=S;i=e[p[i]].x)
            e[p[i]].cap-=Flow,e[p[i]^1].cap+=Flow;
        return Flow;
    }
    LL ISAP(){
        int x=S,y;
        while (dist[S]<n){
            if (x==T){
                MaxFlow+=Augment(x);
                continue;
            }
            bool found=0;
            for (int i=cur[x];i;i=e[i].nxt)
                if (dist[y=e[i].y]+1==dist[x]&&e[i].cap){
                    cur[x]=p[y]=i,x=y,found=1;
                    break;
                }
            if (!found){
                int d=n+1;
                for (int i=fst[x];i;i=e[i].nxt)
                    if (e[i].cap)
                        d=min(d,dist[e[i].y]+1);
                if (!--num[dist[x]])
                    return MaxFlow;
                num[dist[x]=d]++,cur[x]=fst[x],x=x==S?x:e[p[x]].x;
            }
        }
        return MaxFlow;
    }
    LL Auto(int _S,int _T){
        init(_S,_T);
        return ISAP();
    }
}g;
const int N=605;
int T,n,m,k;
int in[N],a[N],b[N];
int k1[N],k2[N],k3[N],ans[N];
vector <int> e[N];
void Out0(){
    for (int i=1;i<=m;i++)
        printf("0 ");
    puts("");
}
void Main(){
    n=read(),m=read(),k=read();
    memset(in,0,sizeof in);
    for (int i=1;i<=n;i++)
        e[i].clear();
    for (int i=1;i<=m;i++){
        a[i]=read(),b[i]=read();
        in[a[i]]++,in[b[i]]++;
    }
    int S=n*2+m*2+1,T=S+1;
    g.clear(T);
    for (int i=1;i<=n;i++){
        if (k*2<in[i])
            return Out0();
        int lim=min(in[i],k*2-in[i]);
        g.add(i,T,lim);
    }
    for (int i=1;i<=m;i++){
        g.add(S,i+n,1),k1[i]=g.cnt;
        g.add(i+n,a[i],1),k2[i]=g.cnt;
        g.add(i+n,b[i],1),k3[i]=g.cnt;
    }
    g.Auto(S,T);
    int totcnt=0;
    for (int i=1;i<=m;i++)
        totcnt+=g.e[k1[i]].cap;
    if (totcnt<m)
        return Out0();
    for (int i=1;i<=m;i++)
        if (g.e[k2[i]].cap){
            e[b[i]].push_back(i);
//            printf("%d pb %d\n",b[i],i);
        }
        else {
            e[a[i]].push_back(i);
//            printf("%d pb %d\n",a[i],i);
        }
    int cnt=0;
    memset(ans,0,sizeof ans);
    for (int i=1;i<=n;i++)
        for (int j=0;j<e[i].size();j++){
            if (j%2==0)
                cnt++;
//            printf(":: %d %d %d %d\n",i,j,e[i][j],cnt);
            ans[e[i][j]]=cnt;
        }
    for (int i=1;i<=m;i++)
        printf("%d ",ans[i]);
    puts("");
}
int main(){
    T=read();
    while (T--)
        Main();
    return 0;
}
CF1070I