都過了一周了,才來補題,不愧是我,摸魚的神。
題意:充n塊錢,不同檔位有首沖獎勵,問最多能得游戲幣。
思路:充n塊錢,能得n*10的游戲幣(一開始沒注意,+1告訴我才發現),然后首沖獎勵每個檔位只有有一次就是01背包了。

1 #include<bits/stdc++.h> 2 using namespace std; 3 typedef long long ll; 4 const int N=2e3+11; 5 const int a[11]={ 6 1,6,28,88,198,328,648, 7 }; 8 const int b[11]={ 9 8,18,28,58,128,198,388, 10 }; 11 int dp[N]; 12 int main(){ 13 int n; 14 scanf("%d",&n); 15 for(int i=0;i<7;++i){ 16 for(int j=n;j>=a[i];--j) dp[j]=max(dp[j],dp[j-a[i]]+b[i]); 17 } 18 printf("%d\n",10*n+dp[n]); 19 return 0; 20 }
題意:
思路:一開始看出an&i打表打錯了,然后按照an&i進行打表,就可以看出比如c是3時
n: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
an:1 3 3 9 3 9 9 27 3 9 9 27 9 27 27 81 3
可以觀察到,從2^i開始到2^(i+1)-1的數是0到2^i-1的數的c倍(又是+1告訴我才發現的,我真是粗心鬼)
當時賽場上是讓+1敲了,我自己做一遍的話思路與賽場上一樣,依舊是一個類似快速冪的思想。
我們可以先預處理出到每一位i時的前n項和,從高位到低位,有1的位就統計答案,同時將下一位的權重增加c倍。
因為是從0開始,然后最高位有個3,那么我們可以先把這個3拿出來,把后面的位的答案加上先,最后再把這個3加回去(此時權重變化,可能就不再是3了)。
比如n=1101,c=3,那么這時就是求1 3 3 9 3 9 9 27 3 9 9 27 9 27這一段的和
單獨看每個1的話,第一個1自然是1 3 3 9 3 9 9 27 3這一段,第二個1是1 3 3 9,第三個1是1 3這一段,
然后我們把第一個1的3先去掉,也就是變成了1 3 3 9 3 9 9 27后面接上3*(1 3 3 9)再接上9*(1 3),最后把27加上。

//可以打表觀察到,從2^i開始到2^(i+1)-1的數是0到2^i-1的數的c倍 //那么我們可以先預處理出到每一位i時的前n項和,然后有1的地方便統計答案 //每統計一次,這個i長度的前綴和就乘c #include<bits/stdc++.h> using namespace std; typedef long long ll; const int N=3e3+11,md=1e9+7; char s[N]; ll a[N]; int main(){ int n,c; a[0]=1; printf("1 "); for(int i=1;i<=16;++i){ ll mx=0; for(int j=0;j<i;++j) mx=max(mx,a[i&j]); a[i]=mx*3; printf("%lld ",a[i]); } scanf("%s%d",s,&c); n=strlen(s); for(int i=1;i<n;++i) a[i]=a[i-1]*(c+1)%md; ll pw=1,ans=0; for(int i=0;i<n;++i){ if(s[i]=='1'){ ans=(ans+pw*a[n-i-1]%md)%md; pw=pw*c%md; } } ans=(ans+pw)%md; printf("%lld\n",ans); return 0; }
題意:樹上的點有權值ai,求
思路:樹啟發模板題,第二次做樹啟題,有點不自信,跟學弟口胡半天沒敢寫,最后按照一開始的思路,直接過了,意料之外的沒有超時。
對於異或結果相同的位置對,我們只需要去拆位,然后統計下每個數相應的每一位有多少個0和1即可。需要注意異或結果超1e6,用來保存結果的桶得開大點。
核心思路便在於,對於當前點u,假設我們已經統計下了某個兒子v1上的權值情況,那么我們可以再去枚舉v2上的點,就可以在v1中去找相應的a[u]^a[v2]計算對答案的貢獻。
那么便根據重鏈和輕鏈的思想,對於當前節點,我們已經保存了重兒子的信息,那么接下來只需要再去枚舉輕兒子即可。但這樣的話只算了輕對重的貢獻,還有輕對輕的貢獻,也要考慮。我的處理是,當前節點跑完某個輕兒子后,再跑一遍這個輕兒子,把它的信息保存下來,算完當前節點子樹對答案的貢獻之后,再又把它輕兒子的信息去掉。

//樹上啟發式,多跑一遍算答案 #include<bits/stdc++.h> using namespace std; typedef long long ll; const int N=1e5+11,M=2e6+11; vector<int> vv[N]; ll ans=0; int a[N],size[N],son[N],cnt[M][21][2]; void dfs1(int u,int f){ size[u]=1; int lenv=vv[u].size(); for(int i=0;i<lenv;++i){ int v=vv[u][i]; if(v==f) continue; dfs1(v,u); size[u]+=size[v]; if(size[v]>size[son[u]]) son[u]=v; } return ; } void add(int x,int val){ for(int i=0;i<20;++i){ if(x&(1<<i)) cnt[a[x]][i][1]+=val; else cnt[a[x]][i][0]+=val; } return ; } void ac(int x,int y){ for(int i=0;i<20;++i){ if(y&(1<<i)) ans+=(1ll<<i)*cnt[x][i][0]; else ans+=(1ll<<i)*cnt[x][i][1]; } return ; } void dot2(int u,int f,int tf,int val){ add(u,val); int lenv=vv[u].size(); for(int i=0;i<lenv;++i){ int v=vv[u][i]; if(v==f||v==son[tf]) continue; dot2(v,u,tf,val); } } void dot1(int u,int f,int tf){ ac(a[u]^a[tf],u); int lenv=vv[u].size(); for(int i=0;i<lenv;++i){ int v=vv[u][i]; if(v==f||v==son[tf]) continue; dot1(v,u,tf);//統計答案 if(u==tf) dot2(v,u,tf,1);//加上輕兒子信息 } if(u==tf){ for(int i=0;i<lenv;++i){ int v=vv[u][i]; if(v==f||v==son[tf]) continue; dot2(v,u,tf,-1);//把輕兒子信息去掉 } } } void dfs2(int u,int f,int op){ int lenv=vv[u].size(); for(int i=0;i<lenv;++i){ int v=vv[u][i]; if(v==f||v==son[u]) continue; dfs2(v,u,0); } if(son[u]) dfs2(son[u],u,1); //多加個函數計算答案 dot1(u,f,u); //正常樹上啟發式過程 dot2(u,f,u,1); if(!op) dot2(u,f,0,-1); } int main(){ int n,u,v; scanf("%d",&n); for(int i=1;i<=n;++i) scanf("%d",&a[i]); for(int i=1;i<n;++i){ scanf("%d%d",&u,&v); vv[u].push_back(v); vv[v].push_back(u); } ans=0; dfs1(1,0); dfs2(1,0,1); printf("%lld\n",ans); return 0; }
題意:一個鎖,有n位數,每位數取值0~9,每次操作可以把某一位+1或者-1%10,有些狀態不能到達。不能操作就輸,問先手能不能贏。
思路:當前+1和學弟這題初步的思路掛了,又沒有什么隊出這題的時候,我還以為這題很難,就沒有深入的想。但這題知道思想后,真不難,我真是個小垃圾,那時沒有繼續的跟隊友進行深入的討論。
之前做過個一般圖博弈,而這個二分圖博弈也類似,勝敗就是看還有沒有增廣路。
每個狀態的數位和要么是奇數要么是偶數,而每次操作便是改變了奇偶。那么狀態可以分成X集合和Y集合,X集合一次操作能到達Y集合某個狀態,便是它們之間存在邊。
那么先手能不能贏,便看加入初狀態后,存不存在一條新的增廣路。用最大流來寫的話,就是看加入初狀態,能不能使得最大流量增加。

#include<bits/stdc++.h> using namespace std; typedef long long ll; const int N=1e5+11,M=N*71,inf=0x3f3f3f3f; struct Side{ int v,ne,w; }S[M]; int ban[N],iso[N]; int n,sn,sb,se,head[N],dep[N],cur[N]; void inito(){ for(int i=0;i<N;++i){ int temp=i,sum=0; while(temp){ sum+=temp%10; temp/=10; } iso[i]=sum&1; } } void init(int n){ sn=0; sb=n-1;se=n; for(int i=0;i<=n;i++){ ban[i]=0; head[i]=-1; } } void addE(int u,int v,int w){ S[sn].w=w;S[sn].v=v; S[sn].ne=head[u]; head[u]=sn++; } void addS(int u,int v,int w){ addE(u,v,w);addE(v,u,0); } bool bfs(int n){ queue<int> q; for(int i=0;i<=n;i++) dep[i]=0; dep[sb]=1; q.push(sb); while(!q.empty()){ int u=q.front(); q.pop(); for(int i=head[u];~i;i=S[i].ne){ int v=S[i].v; cur[v]=head[v]; if(!dep[v]&&S[i].w>0){ dep[v]=dep[u]+1; q.push(v); if(v==se) return 1; } } } return 0; } int dfs(int u,int minf){ if(u==se||!minf) return minf; for(int &i=cur[u];~i;i=S[i].ne){ int v=S[i].v; if(S[i].w>0&&dep[v]==dep[u]+1){ int flow=dfs(v,min(minf,S[i].w)); if(flow>0){ S[i].w-=flow; S[i^1].w+=flow; return flow; } } } return 0; } int dinic(int n){ int ans=0,flow=0; while(bfs(n)){ while(flow=dfs(sb,inf)) ans+=flow; } return ans; } int main(){ inito(); int t; scanf("%d",&t); while(t--){ int n,m,beg,x,lim=1; scanf("%d%d%d",&n,&m,&beg); for(int i=1;i<=n;++i) lim*=10; init(lim+2); while(m--){ scanf("%d",&x); ban[x]=1; } for(int i=0;i<lim;++i){ if(ban[i]) continue; if(iso[i]){ if(i!=beg) addS(sb,i,1); for(int j=1;j<lim;j*=10){ int pw=i/j%10,v1,v2; if(pw==9) v1=i-9*j; else v1=i+j; if(pw==0) v2=i+9*j; else v2=i-j; if(!ban[v1]) addS(i,v1,1); if(!ban[v2]) addS(i,v2,1); } }else if(i!=beg) addS(i,se,1); } dinic(lim+2); if(iso[beg]) addS(sb,beg,1); else addS(beg,se,1); if(dinic(lim+2)) printf("Alice\n"); else printf("Bob\n"); } return 0; }
題意:已經存在一些有限制的圓,再往上加同樣有限制的圓,問有多少種方案。
思路:思路來源
因為半徑只有5,那么每個點能不能在某個圓上只與它周圍10個點有關
設dp[i][j]就是到i點,然后i,i-1,i-2...i-9的狀態為j的方法數,對於狀態j有10位0,1,2,3,4,5,6,7,8,9,第k位為1便說明這個地方被圓包含了
然后右邊界的在i上的圓的左邊界就不能在i-1-k這個位置,所以就枚舉右邊界在i+1上的狀態向i+1進行轉移即可

#include<bits/stdc++.h> using namespace std; typedef long long ll; const int N=1e3+11,M=(1<<10)+11; const int md=1e9+7,sta1=(1<<10)-1,sta2=(1<<5)-1; int dp[N][M],zt1[N],zt2[N]; vector<int> limr[N]; //zt1保存該狀態的左邊界在哪,用來判斷能不能向下一個位置轉移 //zt2保存該狀態覆蓋了那些點,使得這些點不能作為右邊界在下一個位置的圓的左邊界 void init(){ for(int i=0;i<=sta2;++i){ for(int j=0;j<10;j+=2){ if(i&(1<<(j>>1))){ zt1[i]|=1<<(j+1); zt2[i]=max(zt2[i],(1<<(j+1))-1); } } } } int main(){ init(); int n,k,x,r; scanf("%d%d",&n,&k); for(int i=1;i<=k;++i){ scanf("%d%d",&x,&r); limr[x+r].push_back((r-1)<<1); } dp[0][sta1]=1; for(int i=0;i<n;++i){ //f1同zt1,ff1同zt2 int lenv=limr[i+1].size(),f1=0,ff1=0; for(int j=0;j<lenv;++j){ f1|=1<<(limr[i+1][j]+1); ff1=max(ff1,(1<<(limr[i+1][j]+1))-1); } for(int j=0;j<=sta1;++j){ if(!dp[i][j]) continue; int f2=(j<<1)&sta1; if(f1&f2) continue; for(int k=0;k<=sta2;++k){ if(f1&zt1[k]) continue; if(f2&zt1[k]) continue; dp[i+1][ff1|f2|zt2[k]]+=dp[i][j]; dp[i+1][ff1|f2|zt2[k]]%=md; } } } int ans=0; for(int i=0;i<=sta1;++i) ans=(ans+dp[n][i])%md; printf("%d\n",ans); return 0; }
題意:一開始n個帶有權值a[i]的點,m次操作。op 1:添加一個a[x]=y的點x,op 2:把x點和y點所在的樹合並,op 3:修改點x的權值為y。每次操作后,問在有多少在同一樹上的點對滿足gcd(a[x],a[y])=a[x]^a[y]。
思路:對於gcd(x,y)=x^y。假設g=gcd(x,y),x%g=0,那么我們可以枚舉x的約數g,然后就有y=x^g,此時通過判斷gcd(x,y)是否等於g,就可以預處理得到與x滿足條件的所有y,而已可以得出對於每個x與之對於的y不超過60個。那么接下來的操作1,2,3就是並查集的按秩合並了,也就是啟發式的思想。
其中預處理可以優化掉一個求gcd的log,。

#include<bits/stdc++.h> #include<tr1/unordered_map> using namespace std; typedef long long ll; const int N=3e5+11,M=2e5+11; vector<int> vv[M]; int a[N],fa[N],size[N]; tr1::unordered_map<int,int> mmp[N]; tr1::unordered_map<int,int>::iterator it; void init(){ for(int i=1;i<M;++i){ for(int j=i<<1;j<M;j+=i){ int k=(i^j); if(__gcd(j,k)==i) vv[j].push_back(k); } } } void init2(){ for(int i=1;i<M;++i){ for(int j=i<<1;j<M;j+=i){ if(i+j<M&&(i^j)==i+j){ vv[j].push_back(i+j); vv[i+j].push_back(j); } } } } int find(int x){ return fa[x]==x ? x : fa[x]=find(fa[x]); } int main(){ init2(); int n,m; scanf("%d%d",&n,&m); for(int i=1;i<=n;++i){ fa[i]=i;size[i]=1; scanf("%d",&a[i]); ++mmp[i][a[i]]; } ll ans=0; int op,x,y,fx,fy; while(m--){ scanf("%d%d%d",&op,&x,&y); if(op==1){ a[x]=y; fa[x]=x; size[x]=1; ++mmp[x][y]; }else if(op==2){ fx=find(x),fy=find(y); if(fx!=fy){ if(size[fx]>size[fy]) swap(fx,fy); for(it=mmp[fx].begin();it!=mmp[fx].end();++it){ x=it->first;y=it->second; for(int i=0;i<(int)vv[x].size();++i){ if(mmp[fy].count(vv[x][i])) ans+=1ll*y*mmp[fy][vv[x][i]]; } } fa[fx]=fy; size[fy]+=size[fx]; for(it=mmp[fx].begin();it!=mmp[fx].end();++it){ x=it->first;y=it->second; mmp[fy][x]+=y; } size[fx]=0; mmp[fx].clear(); } }else{ fx=find(x); for(int i=0;i<(int)vv[a[x]].size();++i){ if(mmp[fx].count(vv[a[x]][i])) ans-=mmp[fx][vv[a[x]][i]]; } --mmp[fx][a[x]]; for(int i=0;i<(int)vv[y].size();++i){ if(mmp[fx].count(vv[y][i])) ans+=mmp[fx][vv[y][i]]; } a[x]=y; ++mmp[fx][y]; } printf("%lld\n",ans); } return 0; }