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();CF1070Awhile (!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; }
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