- Prob.1 生活大爆炸版 石头剪刀布
模拟。
代码:
#include<cstdio> #include<cstring> #include<iostream> using namespace std; const int mp[5][5]={{0,0,1,1,0}, {1,0,0,1,0}, {0,1,0,0,1}, {0,0,1,0,1}, {1,1,0,0,0}}; int A[205],B[205]; int n,lA,lB,ansA,ansB; int main(){ scanf("%d%d%d",&n,&lA,&lB); for(int i=1;i<=lA;i++) scanf("%d",&A[i]); for(int i=1;i<=lB;i++) scanf("%d",&B[i]); for(int i=1,pA=0,pB=0;i<=n;i++) { pA++; if(pA>lA) pA=1; pB++; if(pB>lB) pB=1; ansA+=mp[A[pA]][B[pB]]; ansB+=mp[B[pB]][A[pA]]; } printf("%d %d",ansA,ansB); return 0; }
- Prob.2 联合权值
可以叫树形dp吧。
每次考虑把当前子树的根作为长度为2的路径的一个端点或中转点。
代码:
#include<cstdio> #include<cstring> #include<iostream> #define MAXN 200005 using namespace std; const int mod=10007; struct edge{ int to,next; }e[MAXN*2]; int w[MAXN],head[MAXN]; int sm[MAXN],mx[MAXN]; int n,ent=2,ansx,anss; void add(int u,int v){ e[ent]=(edge){v,head[u]}; head[u]=ent++; } void dfs(int u,int fa){ for(int i=head[u];i;i=e[i].next){ int v=e[i].to; if(v==fa) continue; dfs(v,u); anss=(1ll*anss+1ll*sm[v]*w[u]*2)%mod; anss=(1ll*anss+1ll*sm[u]*w[v]*2)%mod; sm[u]+=w[v]; ansx=max(ansx,mx[v]*w[u]); ansx=max(ansx,mx[u]*w[v]); mx[u]=max(mx[u],w[v]); } } int main(){ scanf("%d",&n); for(int i=1,u,v;i<n;i++){ scanf("%d%d",&u,&v); add(u,v); add(v,u); } for(int i=1;i<=n;i++) scanf("%d",&w[i]); dfs(1,0); printf("%d %d",ansx,anss); return 0; }
- Prob.3 飞扬的小鸟
dp+转移优化
首先 70%的很好想:
dp[i][j]表示到达(i,j)的最小点击次数,显然没有后效性。
┌dp[i-1][j+y[i-1]] (不点击)
dp[i][j]=min│
┕dp[i-1][j-k*x[i-1]]+k(点击)
这个方程的复杂度为 (n*m*m)
考虑优化,发现这样一个东西:
只考虑"点击"这一个转移来源的话,那么
dp[i][j]只比dp[i][j-x[i-1]]多了一个转移来源。
然后就是一个前缀的思想了,即我们把第二维的j从小到大枚举
那么(这里在说"点击"这一个来源)
dp[i][j]=min(dp[i-1][j-x[i-1]],dp[i][j-x[i-1])+1
这样就把"点击"时的O(n)转移优化为了O(1)。
注意j==m时要特殊处理。
代码:
#include<cstdio> #include<cstring> #include<iostream> #define INF 0x3f3f3f3f using namespace std; int x[10005],y[10005],l[10005],h[10005],fg[10005]; int dp[10005][1005]; int n,m,k,ans=INF; bool legal(int i,int j){ if(j<=0||j>m) return 0; if(!fg[i]) return 1; return l[i]<j&&j<h[i]; } void cmin(int &a,int b){ if(a>b) a=b; } int main(){ scanf("%d%d%d",&n,&m,&k); memset(dp,0x3f,sizeof(dp)); for(int i=0;i<n;i++) scanf("%d%d",&x[i],&y[i]); for(int i=1,p;i<=k;i++) scanf("%d",&p),fg[p]=1,scanf("%d%d",&l[p],&h[p]); for(int j=l[0]+1;j<(fg[0]?h[0]:m+1);j++) dp[0][j]=0; for(int i=1;i<=n;i++){ //click for(int j=0;j<m;j++){ int a=legal(i-1,j-x[i-1])?dp[i-1][j-x[i-1]]:INF; int b=j>x[i-1]?dp[i][j-x[i-1]]:INF; cmin(dp[i][j],min(a,b)+1); } for(int j=m;j>=0;j--){ int a=legal(i-1,j)?dp[i-1][j]:INF; cmin(dp[i][m],a+max(m-j-1,0)/x[i-1]+1); } //no-click for(int j=0;j<=m;j++){ int a=legal(i-1,j+y[i-1])?dp[i-1][j+y[i-1]]:INF; cmin(dp[i][j],a); } } for(int i=n;i>=0;i--){ for(int j=1;j<=m;j++){ if(i==n) cmin(ans,dp[i][j]); if(i!=n&&legal(i,j)&&dp[i][j]<INF){ printf("%d %d",0,k); return 0; } } if(ans<INF) break; if(fg[i]) k--; } printf("%d %d",1,ans); return 0; }
- Prob.4 无线网络发射器选址
二维前缀和,求子矩阵和。
千万不要枚举多了。否则会多统计。
代码:
#include<cstdio> #include<cstring> #include<iostream> using namespace std; int sum[150][150]; int d,n,ans,cnt; int area(int x1,int y1,int x2,int y2){ return sum[x2][y2]-sum[x1][y2]-sum[x2][y1]+sum[x1][y1]; } int main(){ scanf("%d%d",&d,&n); for(int i=1,x,y,k;i<=n;i++){ scanf("%d%d%d",&x,&y,&k); x++;y++; sum[x][y]+=k; } for(int i=1;i<=129;i++) for(int j=1;j<=129;j++) sum[i][j]=sum[i][j]+sum[i-1][j]+sum[i][j-1]-sum[i-1][j-1]; for(int i=1,x1,y1,x2,y2,now;i<=129;i++) for(int j=1;j<=129;j++){ x1=max(i-d-1,0); y1=max(j-d-1,0); x2=min(i+d,129); y2=min(j+d,129); now=area(x1,y1,x2,y2); if(now>ans) ans=now,cnt=0; if(now==ans) cnt++; } printf("%d %d",cnt,ans); return 0; }
- Prob.5 寻找道路
反向BFS给无法到终点的点打上标记,
然后再给那些存在一条出边无法到终点的点打上标记。
最后对没有标记的点跑最短路。(因为边权为1,BFS即可)
代码:
#include<queue> #include<cstdio> #include<cstring> #include<iostream> using namespace std; struct edge{ int to,next; }e1[200005],e2[200005]; int head1[10005],head2[10005],vis[10005],dis[10005]; int n,m,ent1=2,ent2=2,S,T; void add(int u,int v,int &ent,int *head,edge *e){ e[ent]=(edge){v,head[u]}; head[u]=ent++; } void BFS1(){ queue<int> q; vis[T]=1; q.push(T); while(!q.empty()){ int u=q.front();q.pop(); for(int i=head2[u];i;i=e2[i].next){ int v=e2[i].to; if(vis[v]) continue; vis[v]=1; q.push(v); } } for(int u=1;u<=n;u++) if(!vis[u]) for(int i=head2[u];i;i=e2[i].next){ int v=e2[i].to; if(!vis[v]) continue; vis[v]=-1; } } void BFS2(){ queue<int>q; q.push(S); dis[S]=1; while(!q.empty()){ int u=q.front();q.pop(); for(int i=head1[u];i;i=e1[i].next){ int v=e1[i].to; if(dis[v]||vis[v]!=1) continue; dis[v]=dis[u]+1; q.push(v); } } if(dis[T]==0) printf("-1"); else printf("%d",dis[T]-1); } int main(){ scanf("%d%d",&n,&m); for(int i=1,u,v;i<=m;i++) scanf("%d%d",&u,&v), add(u,v,ent1,head1,e1), add(v,u,ent2,head2,e2); scanf("%d%d",&S,&T); BFS1(); BFS2(); return 0; }
- Prob.6 解方程
又是一大神题,车大佬给我讲了好久才明白。
首先按照题目的意思,我们找出x,要使得:
A0 + A1*X1 + A2*X2 + A3*X3 + ...... + An*Xn == 0 [1]
但是无奈 A的范围很大,无法直接计算。
(然后就阴差阳错的考虑到取模上去)
对等式两边同时取模 M(先只对系数取模)
那么得到:(Bi = Ai % M)
B0 + B1*X1 + B2*X2 + B3*X3 + ...... + Bn*Xn == 0 [2]
然后动动脑子,可以发现满足 [1]式的x 则是一定满足 [2]式的x。
!!!
那么我们是不是可以把原来的 [1]式 通过取模后得到系数较小的 [2]式,
然后通过 可以计算的[2]式来找合法的x。
好吧,显然有错。
虽然满足 [1]式的x 一定是满足 [2]式的x,
但是满足 [2]式的x 却不一定是满足[1]式的x。(这个好理解吧)
显然满足 [2]式的x,带入[1]式左边,可能得到两个值,一个是0(合法),一个是k * M(非法)
那怎么办? 接下来就神奇了。
我们多选几个 M(5-6个) : M1 , M2 , M3 ,M4 , M5(M尽量为大素数或某些大素数的乘积)
那么对于一个x,在这5种M取模下,如果都满足 [2]式(即其值==0),那么这个x就非常可能是满足 [1]式的,直接把它当做答案。
为什么呢。
如果都满足,那么把这个 x 带入 [1]式后,得到的值要么为 0(合法),要么为 k * M1M2M3M4M5(非法)。
(对于非法的 x,就是带入 [1]式后,其值==k * M1M2M3M4M5,即是"我们选取的这几个模值的乘积的倍数"。)
而对于所有的x,带入 [1]式后,最多产生 10^6个值。
那么在出题人不知道我们选取的模值的情况下,要使得某个x带入 [1]式后恰好等于"我们选取的模数的乘积的倍数"的几率是很小的。
所以,除非运气不好,否则我们选的几个M (强调:M尽量为大素数或某些大素数的乘积),就可以达到筛选出正确的x的目的。
#include<cctype> #include<cstdio> #include<cstring> #include<iostream> #define _ %mod[j] using namespace std; const int mod[6]={13923, 16851, 24989, 19997, 11987, 13999}; int val[1000005][6]/*mod意义下*/,A[105][6],ANS[1000005]; int n,m,cnt; void readai(){ int fu;char ch; ch=getchar(); for(int i=0;i<=n;i++){ fu=1; while(!isdigit(ch)){ if(ch=='-') fu=-1; ch=getchar(); } while(isdigit(ch)){ for(int j=0;j<=5;j++) A[i][j]=(A[i][j]*10+(ch-'0'))_; ch=getchar(); } for(int j=0;j<=5;j++) A[i][j]*=fu; } } int Pow(int a,int b,int j){ int now=1; while(b){ if(b&1) now=(1ll*now*a)_; a=(1ll*a*a)_; b>>=1; } return now; } int cal(int x,int j){ int ans=0; for(int i=0;i<=n;i++) ans=(ans+(1ll*A[i][j]*Pow(x,i,j))_)_; return ans; } int main(){ scanf("%d%d",&n,&m); readai(); for(int j=0;j<=5;j++) for(int i=1;i<mod[j];i++) val[i][j]=cal(i,j); for(int i=1;i<=m;i++){ bool fg=1; for(int j=0;j<=5;j++) if(val[(i)_][j]) fg=0; if(fg) ANS[++cnt]=i; } printf("%d ",cnt); for(int i=1;i<cnt;i++) printf("%d ",ANS[i]); printf("%d",ANS[cnt]); return 0; }