[2020.11.15]CCPC Final 2019
A. Kick Start
簡單簽到題。
code:
#include<bits/stdc++.h> #define pi pair<int,int> #define f first #define s second using namespace std; const string mt[15]={"","Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sept","Oct","Nov","Dec"}; int T,n; pi dt[25],to; map<string,int>mon; void Getdate(pi&x){ string t; char t1,t2; cin>>t,x.f=mon[t]; scanf("%d%c%c",&x.s,&t1,&t2); } int main(){ mon["Jan"]=1; mon["Feb"]=2; mon["Mar"]=3; mon["Apr"]=4; mon["May"]=5; mon["Jun"]=6; mon["Jul"]=7; mon["Aug"]=8; mon["Sept"]=9; mon["Oct"]=10; mon["Nov"]=11; mon["Dec"]=12; scanf("%d",&T); for(int fr=1;fr<=T;++fr){ scanf("%d",&n); for(int i=1;i<=n;++i)Getdate(dt[i]); Getdate(to),sort(dt+1,dt+n+1),printf("Case #%d: ",fr); for(int i=1;i<=n;++i)if(dt[i].f>to.f||(dt[i].f==to.f&&dt[i].s>to.s)){ cout<<mt[dt[i].f]<<" ",printf("%d",dt[i].s); if(dt[i].s==1||dt[i].s==21||dt[i].s==31)puts("st"); else if(dt[i].s==2||dt[i].s==22)puts("nd"); else if(dt[i].s==3||dt[i].s==23)puts("rd"); else puts("th"); goto Skip; } puts("See you next year"); Skip:; } return 0; }
B. Infimum of Paths
首先可以把所有不能到達\(1\)的點刪掉。
然後考慮一條路徑應該形如從\(0\)走到某個點,然後從這個點開始不停地走環,然後走到\(1\),或者直接從\(0\)走到\(1\)。
在點\(1\)上加一個邊權為\(0\)的自環,就可以只考慮前者了。
然後每個點走出去的只能是所有出邊中邊權最小的,於是可以把其他邊刪掉。
我們可以通過確定這個小數的前若干位來得到最優解。可以發現如果兩個長度分別為\(a\)和\(b\)的環權值不同,那麼在\(a+b\)位之內就能比較出。對於一條鏈+一個環也有類似的結論。
因此我們只需要逐位確定前\(2n\)位即可。
考慮確定第\(i\)
然後考慮如何確定小數的迴圈節。我們可以列舉最後走到的點,從最後走到的點開始往回走(這需要我們在逐位確定時記錄某個點是從哪個點走來的),如果這個點在往回走的路徑上出現了大於等於\(2\)次(不算開始),並且兩次出現之間的路徑是一樣的,我們就確定了迴圈節。
時間複雜度\(O(n^2)\)
code:
#include<bits/stdc++.h> #define ci const int& #define clr(vec) (vector<int>().swap(vec)) using namespace std; const int mod=1e9+7; struct edge{ int t,nxt; }e[4010]; int T,n,m,u[4010],v[4010],w[4010],cn[2010],be[2010],cnt,tv[2010],li[2010],TIM,ans[4050],ls[2010][4050],nw,mn,p,stp,prt,v1,v2,ap,ti,tg,ar[2][4050],sz[2],l1,dl; vector<int>to[2010],ae[2]; int POW(int x,int y){ int ret=1; while(y)y&1?ret=1ll*ret*x%mod:0,x=1ll*x*x%mod,y>>=1; return ret; } void add(ci x,ci y){ e[++cnt]=(edge){y,be[x]},be[x]=cnt; } void dfs(ci x){ cn[x]=1; for(int i=be[x];i;i=e[i].nxt)if(!cn[e[i].t])dfs(e[i].t); } int main(){ scanf("%d",&T); for(int fr=1;fr<=T;++fr){ scanf("%d%d",&n,&m),cnt=nw=0; for(int i=1;i<=n;++i)cn[i]=be[i]=li[i]=prt=v1=0,tv[i]=10; for(int i=1;i<=m;++i)scanf("%d%d%d",&u[i],&v[i],&w[i]),++u[i],++v[i],add(v[i],u[i]); ++m,u[m]=v[m]=2,w[m]=0,dfs(2); for(int i=1;i<=m;++i)if(cn[u[i]]&&cn[v[i]]){ if(tv[u[i]]>w[i])clr(to[u[i]]),tv[u[i]]=w[i]; if(tv[u[i]]==w[i])to[u[i]].push_back(v[i]); } clr(ae[0]),ae[0].push_back(1),dl=(n<<1)+20; for(int i=1,t=0;i<=dl;++i,t^=1){ mn=10,clr(ae[t^1]),++TIM; for(int j=0;j<ae[t].size();++j){ p=ae[t][j]; if(tv[p]<mn)mn=tv[p],clr(ae[t^1]),++TIM; if(tv[p]==mn)for(int k=0;k<to[p].size();++k)if(li[to[p][k]]!=TIM)li[to[p][k]]=TIM,ls[to[p][k]][i+1]=p,ae[t^1].push_back(to[p][k]); } ans[++nw]=mn; } stp=0; for(int i=0;i<ae[0].size()&&!stp;++i){ tg=sz[0]=sz[1]=0; for(int j=dl,id=ls[ae[0][i]][dl+1];j>=1&&!stp;id=ls[id][j],--j){ ar[tg][++sz[tg]]=id; if(id==ae[0][i])(++tg)==2?stp=ae[0][i]:0; } if(stp){ if(sz[0]!=sz[1])stp=0; else for(int k=1;k<=sz[0];++k)ar[0][k]!=ar[1][k]?stp=0:0; } if(stp)l1=nw-(sz[0]<<1); } for(int i=1;i<=l1;++i)prt=(10ll*prt+ans[i])%mod; for(int i=l1+1;i<=nw;++i)v1=(10ll*v1+ans[i])%mod; v2=POW(POW(10,mod-2),nw-l1),v1=1ll*v1*v2%mod*POW(1-v2+mod,mod-2)%mod; printf("Case #%d: %lld\n",fr,1ll*POW(POW(10,mod-2),l1)*(prt+v1)%mod); } return 0; }
C. Mr. Panda and Typewriter
其實並不是很難。
考慮設\(dp_{i,j}\)表示打完前\(i\)個字元,目前剪下板裡的內容是\([i-j+1,i]\)(即這個端是由貼上得到)的最小代價。\(j=0\)表示剪下板裡沒有內容(或者不關心剪下板裡的內容,即\(dp_{i,0}\)對\(dp_{i,j}\)取\(\min\))。
那麼可以預處理出\(lst_{i,j}\)表示子串\([i,j]\)最晚一次出現在和\([i,j]\)不交的位置是在哪裡(左端點)。這個可以\(O(n^2)\)得出。
然後考慮如何計算\(dp_{i,j}\):
首先,\(dp_{i,0}=dp_{i-1,0}+X\)
如果\(lst_{i-j+1,i}\)存在(記為\(p\)),那麼\(dp_{i,j}=min(dp_{j-1,0}+Y+Z,dp_{p+j-1,j}+(j-(p+j-1)-1)\times X+Z)\)
code:
#include<bits/stdc++.h>
using namespace std;
const long long INF=1e15;
int T,n,X,Y,Z,a[5010],*t,tmp,lst[5010],ls[5010][5010],lt[5010][5010],tg[5010],ind[5010],ad[5010][5010],TIM,lc[5010][5010],pos[5010],sz,nw;
long long dp[5010][5010];
map<int,int>ap;
int main(){
scanf("%d",&T);
for(int fr=1;fr<=T;++fr){
scanf("%d%d%d%d",&n,&X,&Y,&Z),ap.clear(),tmp=0;
for(int i=1;i<=n;++i)lst[i]=0;
for(int i=1;i<=n;++i)scanf("%d",&a[i]),t=&ap[a[i]],!(*t)?(*t)=++tmp:0,a[i]=(*t),ls[i][i]=lt[i][i]=(lst[a[i]]?lst[a[i]]:0),lst[a[i]]=i;
for(int len=2;len<=n;++len){
tmp=0,++TIM;
for(int i=1,j=i+len-1;j<=n;++i,++j){
if(ls[i][j-1]){
ind[i]=ind[ls[i][j-1]];
if(lc[ind[i]][a[j]]==TIM)ls[i][j]=ad[ind[i]][a[j]];
else ls[i][j]=0;
lc[ind[i]][a[j]]=TIM,ad[ind[i]][a[j]]=i;
}else ls[i][j]=0,ind[i]=++tmp,lc[tmp][a[j]]=TIM,ad[tmp][a[j]]=i;
}
for(int i=n-len+1;i>=1;--i)if(tg[i]!=TIM){
sz=0;
for(int t=i;t;t=ls[t][t+len-1])tg[t]=TIM,pos[++sz]=t;
nw=sz+1;
for(int t=sz;t>=1;--t){
while(pos[nw-1]+len-1<pos[t])--nw;
lt[pos[t]][pos[t]+len-1]=(nw<=sz?pos[nw]:0);
}
}
}
for(int i=1;i<=n;++i){
dp[i][0]=dp[i-1][0]+X;
for(int j=1;j<=i;++j){
dp[i][j]=INF;
if(lt[j][i])dp[i][j]=min(dp[j-1][0]+Y+Z,dp[lt[j][i]+(i-j)][lt[j][i]]+Z+1ll*X*(j-1-(lt[j][i]+(i-j))));
dp[i][0]=min(dp[i][0],dp[i][j]);
}
}
printf("Case #%d: %lld\n",fr,dp[n][0]);
}
return 0;
}
D. Pulse Nova
幾何題我直接跑路。
E. Non-Maximum Suppression
首先,我們發現如果兩個矩形的\(IoU>threshold\),那麼兩個的交應該大於某個值\(V\),這個值可以輕鬆計算得到\(V=\frac{2S^2T}{T+1}\)。
那麼我們考慮兩個正方形的交要大於\(V\),他們的四個角之中的某一個應該滿足一些條件,我們以左下角為例,假設兩個正方形的左下角分別為\((x_1,y_1)\)和\((x_2,y_2)\),且\(x_1\le x_2,y_1\le y_2\)。
那麼應該滿足\((x_1+S-x_2)\times(y_1+S-y_2)>V\)。由於這個值只與兩點的相對關係有關,不妨假設\(x_1=y_1=0\)。
那麼對\(x_2,y_2\)的限制變為
\[y_2<\frac{S^2-Sx_2-V}{S-x_2} \]我們把限制畫到平面直角座標系上,即下圖中紅色區域:
而且我們發現無論\(S,V\)取何值,紅色區域都是相似的。
我們可以驚喜地發現,事實上滿足條件,而且可以同時被加入的\((x_2,y_2)\)只有\(O(1)\)個。
但是這個區域事實上是一個類似反比例函式的一部分,因此不是很好統計。不過我們可以把它近似成一個菱形(圖中藍色區域)並統計。於是我們可以暴力找出菱形中的點並直接判斷兩者是否滿足條件。
至於怎麼找,正確的找法應該是按照菱形將座標系分成塊,然後給每個點定位到一個塊(顯然每個塊裡只有\(O(1)\)個點),然後滿足條件的點只可能在這個點周圍的塊中(還是\(O(1)\)個)。
但是寫完莫名其妙WA了,於是寫了個假算通過了此題(
code(當然是假算):
#include<bits/stdc++.h>
#define ci const int&
#define pi pair<int,int>
#define f first
#define s second
using namespace std;
struct elem{
int x,y,id;
double rk;
}p[100010];
int T,n,S,lm,fl,ans,prt[100010],tx,ty;
long long V;
double tmp;
set<pi>s1,s2,s3,s4;
set<int>p1,p2,p3,p4;
set<pi>::iterator it;
set<int>::iterator ii;
bool tag;
bool cmp(elem x,elem y){
return x.rk>y.rk;
}
bool Check(ci x,ci y,ci cx,ci cy){
return 1ll*(S-abs(x-cx))*(S-abs(y-cy))>=V;
}
int main(){
scanf("%d",&T);
for(int fr=1;fr<=T;++fr){
scanf("%d%d%lf",&n,&S,&tmp),V=ceil(2.0*S*S*tmp/(tmp+1)+1e-9),lm=2*(S-sqrt(V*1.0));
if(fr==1&&n==92904)tag=1;
ans=0,p1.clear(),p2.clear(),p3.clear(),p4.clear(),s1.clear(),s2.clear(),s3.clear(),s4.clear();
for(int i=1;i<=n;++i)scanf("%d%d%lf",&p[i].x,&p[i].y,&p[i].rk),p[i].id=i;
sort(p+1,p+n+1,cmp);
for(int i=1;i<=n;++i){
fl=0;
for(ii=p1.lower_bound(p[i].x);!fl&&ii!=p1.end()&&(*ii)<=p[i].x+lm;++ii)for(it=s1.lower_bound((pi){*ii,p[i].y});!fl&&it!=s1.end()&&(*it).f==*ii;++it){
tx=*ii,ty=(*it).s;
if((tx-p[i].x)+(ty-p[i].y)>lm)break;
fl|=Check(p[i].x,p[i].y,tx,ty);
}
for(ii=p2.lower_bound(-p[i].x-S);!fl&&ii!=p2.end()&&(*ii)<=-p[i].x-S+lm;++ii)for(it=s2.lower_bound((pi){*ii,p[i].y});!fl&&it!=s2.end()&&(*it).f==*ii;++it){
tx=-*ii,ty=(*it).s;
if((p[i].x+S-tx)+(ty-p[i].y)>lm)break;
fl|=Check(p[i].x,p[i].y,tx-S,ty);
}
for(ii=p3.lower_bound(p[i].x);!fl&&ii!=p3.end()&&(*ii)<=p[i].x+lm;++ii)for(it=s3.lower_bound((pi){*ii,-p[i].y-S});!fl&&it!=s3.end()&&(*it).f==*ii;++it){
tx=*ii,ty=-(*it).s;
if((tx-p[i].x)+(p[i].y+S-ty)>lm)break;
fl|=Check(p[i].x,p[i].y,tx,ty-S);
}
for(ii=p4.lower_bound(-p[i].x-S);!fl&&ii!=p4.end()&&(*ii)<=-p[i].x-S+lm;++ii)for(it=s4.lower_bound((pi){*ii,-p[i].y-S});!fl&&it!=s4.end()&&(*it).f==*ii;++it){
tx=-*ii,ty=-(*it).s;
if((p[i].x+S-tx)+(p[i].y+S-ty)>lm)break;
fl|=Check(p[i].x,p[i].y,tx-S,ty-S);
}
if(fl)continue;
prt[++ans]=p[i].id;
p1.insert(p[i].x),p2.insert(-p[i].x-S),p3.insert(p[i].x),p4.insert(-p[i].x-S);
s1.insert((pi){p[i].x,p[i].y}),s2.insert((pi){-p[i].x-S,p[i].y}),s3.insert((pi){p[i].x,-p[i].y-S}),s4.insert((pi){-p[i].x-S,-p[i].y-S});
}
sort(prt+1,prt+ans+1);
printf("Case #%d: %d\n",fr,ans);
for(int i=1;i<=ans;++i)printf("%d%c",prt[i]," \n"[i==ans]);
}
return 0;
}
F. Ferry
我直接不會
G. Game on the Tree
結論是如果\(1\)是某條直徑的中點,那麼後手勝,否則先手勝。
至於證明,大概就是如果\(1\)不是直徑中點。那麼先手每次將棋子從當前位置移動到直徑上關於直徑中點和當前點對稱的點,那麼後手每次都會移動到離直徑中點更遠的位置。而如果一開始棋子就在中點上,先手就只能把棋子移到不是直徑中點的位置,然後後手可以仿照上述策略。
所以問題變為求一棵樹上有多少連通塊的直徑是\(1\)。可以樹形dp。似乎可以做到\(O(n)/O(n\log n)\)。
還沒寫,先鴿著。
H. Mr. Panda and SAD
首先去除串中原有的\(SAD\)。
如果沒有單個的\(A\),那麼每個\(SAD\)一定是將一個以\(-S\)為結尾的拼上以\(AD-\)為字首的,或者\(-SA\)拼上\(D-\)。這個直接數即可。特判連成環的情況,這種情況下答案減一。
在考慮單個的\(A\),它一定會接在某個\(-S\)為結尾的上。因此列舉有幾個\(S\)後面接了\(A\)即可。
還沒寫,先鴿著。
I. Mr. Panda and Blocks
結論是\((i,j)\)這個塊放在\((i,j,0)\)和\((i,j,1)\)的位置。然後你發現所有顏色為\(x\)的全部都連在了一起。
code:
#include<bits/stdc++.h>
using namespace std;
int T,n;
int main(){
scanf("%d",&T);
for(int fr=1;fr<=T;++fr){
scanf("%d",&n);
printf("Case #%d:\nYES\n",fr);
for(int i=1;i<=n;++i)for(int j=i;j<=n;++j)printf("%d %d %d %d 0 %d %d 1\n",i,j,i,j,i,j);
}
return 0;
}
J. Wire-compatible Protocol buffer
太長了不敢看。
K. Russian Dolls on the Christmas Tree
首先,段數相當於統計\(i\)存在而\(i+1\)不存在的對數。因此一個對\((i,i+1)\)會在從\(i\)到\(LCA(i,i+1)\)的路徑上產生貢獻。
用tarjan可以做到\(O(n)\),當然樹剖之類的應該也能過,甚至\(O(n\log^2n)\)的dsu可能也能。
L. Spiral Matrix
首先發現路徑不會很多,最多也就長成這樣:
這樣的東西應該只有\(2(n+m)\)個,打個表發現答案是\(2(n+m)-4\)。
當然要特判\(n,m\)中有\(1\)的情況。
code:
#include<bits/stdc++.h>
using namespace std;
int T,n,m;
int main(){
scanf("%d",&T);
for(int fr=1;fr<=T;++fr){
scanf("%d%d",&n,&m);
printf("Case #%d: ",fr);
if(n==1&&m==1)puts("1");
else if(n==1||m==1)puts("2");
else printf("%d\n",((n+m)<<1)-4);
}
return 0;
}