2022暑期多校(牛客+杭电)
2022.7.18 牛客
A
队友写的
D
高中数学(
考虑弦长最大的时候,弧长最大。
于是画画图就会发现其实那个弦长最大的时候是和共线
然后连一下和跟,数学推导一下弦长,再通过弦长反推圆心角大小就好了。
记得别写反和
#include <bits/stdc++.h> using namespace std; int T; const double PI = 3.141592653589793; int main(){ cin>>T; while (T--){ double R,x,y,d; scanf("%lf%lf%lf%lf",&R,&x,&y,&d); if(fabs(d-R) <=0.00001){ printf("%.10lf\n",PI*R); continue; } double dis = sqrt(x*x+y*y); double t1=sqrt(R*R-(dis+d)*(dis+d)); double t2=sqrt(R*R-(dis-d)*(dis-d)); double Len=t2-t1; double Len1=sqrt(Len*Len+2.0*d*2.0*d); Len1=Len1/2.f; double Cos = Len1/R; double Angle = asin(Cos); double ans = 2.0 * Angle * R; printf("%.10lf\n",ans); } return 0; }
G
签到
显然填一堆是最优的
然后记得判断原串是不是的形式就行了(
#include <bits/stdc++.h> using namespace std; string ss; int main(){ cin>>ss; int Len=ss.length(); if (Len == 1){ cout<<ss; return 0; } bool flag=false; for (int i=0;i<Len-1;i++){ if (ss[i]!='9') flag=true; } if (flag) for (int i=1;i<=Len-1;i++){ printf("9"); }else cout<<ss; return 0; }
I
血压暴涨题(
发现打牌轮数是固定的
直接就好了
然后记得打麻将的时候每一轮手上是张牌
写错这个调了好久
表示当前是第轮,个对子
每次可以多拿一个对子,或者不变
但是因为是最优策略,所以多拿一个对子的话,那张单牌一定还有张在牌山里。
然后没了(
#include <bits/stdc++.h> using namespace std; long long dp[170][10]; int T; long long pai=136; int Pai[5][15]; long long Ans[55]; string ss; long long fish = 1e9+7; long long Pow(int x,int y){ long long ans=1; for (;y;y>>=1,x=1ll*x*1ll*x%fish) if (y&1) ans=1ll*ans*1ll*x%fish; return ans; } void Get(int x){ memset(dp,0,sizeof(dp)); pai = 136; pai = pai - 13; dp[0][x] = 1; for (int i=0 ; i<=pai ; i++) for (long long j=0;j<=6;j++){ long long res = (13ll-2ll*j)*3ll; long long dan = pai-i; long long nw = 1ll*dp[i][j]*Pow(dan,fish-2)%fish; if (dan-res >= 0){ (dp[i+1][j+1] += 1ll*nw*res%fish)%=fish; (dp[i+1][j] +=1ll*nw*(dan-res)%fish)%=fish; } } long long ans=0; for (int i=1;i<=pai-1;i++){ ans = (ans + 1ll * dp[i][7] * i%fish)%fish; } Ans[x]=ans; } int cnt=0; int main(){ scanf("%d",&T); for (int i=0;i<=7;i++){ Get(i); } while (T--){ cnt++; cin>>ss; memset(Pai,0,sizeof(Pai)); int Len=ss.length(); int x,hua,dui = 0; for (int i=0;i<Len;i++){ if ('1' <= ss[i] && ss[i] <= '9'){ x=ss[i]-'0'; i++; if (ss[i] == 'm') hua = 1; if (ss[i] == 'p') hua = 2; if (ss[i] == 's') hua = 3; if (ss[i] == 'z') hua = 4; Pai[hua][x]++; if (Pai[hua][x] == 2){ dui++; } } } printf("Case #%d: %lld\n",cnt,Ans[dui]); } return 0; }
补题:
C
首先,先考虑一个点的遮挡范围
把两个端点和这个点连起来,会形成一个三角区域
那就是它的遮挡范围
把线分为从上往下和从上往下两个方向
对于同一方向的线来说:
而显然,斜率越大,在同一行遮挡范围越大
那么其实,每一行只有第一个点是遮挡点
那么,每次就考虑把第一个点抓出来,维护一个最大斜率,对每一行算出最大斜率的遮挡位置,取个。
两个方向分开来算。
正着跑一遍,反着跑一遍即可。
具体可以画画图
#include <bits/stdc++.h> using namespace std; int N,M,K,T; set<int> a[200005]; int mn[200005]; pair<int,int> nw[200005]; long long mn1[200005]; long long solve(){ long long ans=0; double K=0; for (int i=1;i<=M;i++) mn1[i]=N; for (int i=1;i<=M;i++){ if (i>1){ double nK=double(i-1)/(double)mn[i]; if (mn[i]!=N+1) K=max(K,nK); mn1[i] =int( min(double (N),min (double(mn1[i]),(i-1)/K - 0.000001) )); } else if (a[i].size()) mn1[i]=(*a[i].begin())-1; else mn1[i]=N; } K=0; for (int i=M;i>=1;i--){ if (i<M){ double nK=double(M-i)/mn[i]; if (mn[i]!=N+1) K=max(K,nK); mn1[i] =int( min(double(N),min (double(mn1[i]),(M-i)/K-0.0000001) )); } else if (a[i].size()) mn1[i]=min(mn1[i],(long long)(*a[i].begin())-1); ans+=1ll*mn1[i]; } return ans; } int main(){ scanf("%d%d%d%d",&N,&M,&K,&T); for (int i=1;i<=K;i++){ int x,y; scanf("%d%d",&x,&y); a[y].insert(x); nw[i] = {x,y}; } for (int i=1;i<=M;i++){ if (a[i].empty()) mn[i]=N+1; else mn[i]=*a[i].begin(); } while (T--){ int id,x,y; scanf("%d%d%d",&id,&x,&y); int X=nw[id].first,Y=nw[id].second; a[Y].erase(X); if (a[Y].empty()) mn[Y]=N+1; else mn[Y] = *a[Y].begin(); a[y].insert(x); nw[id]={x,y}; mn[y]=*a[y].begin(); printf("%lld\n",solve()); } return 0; }
7.19杭电
B
墙<=15,随便搜搜就好了。
I
可以去判断所有可能的情况分类讨论
现场写的做法是每次随机两个点和他们处于的线是哪一条
如果所有的点都在这个线上,那么这样随机一次,两条都正确的概率是
而错误率是
多随机几次就能把错误率降低到一个合适的范围。
#include <bits/stdc++.h> using namespace std; bool flag=false; int N; int x[500005],y[500005]; bool vis[500005]; bool Check(int X,int Y){ for (int i=1;i<=N;i++){ if (x[i]==X || y[i]==Y || y[i]-Y==x[i]-X || x[i]+y[i] == X+Y) vis[i]=true; } for (int i=1;i<=N;i++) if (!vis[i]) return false; flag=true; return true; } int Random(int x){ return 1ll*rand()*rand()%x*1ll*rand()%x+1; } int main(){ int T; srand(time(NULL)); scanf("%d",&T); while (T--){ scanf("%d",&N); flag=false; for (int i=1;i<=N;i++) scanf("%d%d",&x[i],&y[i]); for (int i=1;i<=1000;i++){ int TypeA=rand()%4+1,TypeB=rand()%4+1; while (TypeA == TypeB) TypeB=rand()%4+1; if (TypeA>TypeB) swap(TypeA,TypeB); for (int j=1;j<=N;j++) vis[j]=false; int P1=Random(N),P2=Random(N); int PointX,PointY; if (TypeA == 1 && TypeB == 2){ PointX=x[P1],PointY=y[P2]; Check(PointX,PointY); if (flag){ printf("YES\n"); break; } } if (TypeA == 1 &&TypeB==3){ PointX=x[P1],PointY=PointX+y[P2]-x[P2]; Check(PointX,PointY); if (flag){ printf("YES\n"); break; } } if (TypeA == 1 && TypeB == 4){ PointX=x[P1],PointY=y[P2]+x[P2]-PointX; Check(PointX,PointY); if (flag){ printf("YES\n"); break; } } if (TypeA == 2 && TypeB == 3){ PointX=x[P2]+y[P2]-PointY,PointY=y[P1]; Check(PointX,PointY); if (flag){ printf("YES\n"); break; } } if (TypeA==2 && TypeB == 4){ PointX=PointY-(y[P2]-x[P2]),PointY=y[P1]; Check(PointX,PointY); if (flag){ printf("YES\n"); break; } } if (TypeA == 3 && TypeB == 4){ PointY=(y[P1]+x[P1]+y[P2]-x[P2])/2; PointX=(y[P1]+x[P1]-y[P2]+x[P2])/2; if (Check(PointX,PointY)){ printf("YES\n"); break; } } } if (!flag) printf("NO\n"); } return 0; }
K
每个点的期望是
算个逆元乘一下就好。
L
考虑每次都尽量的等分
考虑第个数字,它如果进行次等分后还有剩余,就说明赢
那么每次,在后会变成,倒着加回去就行了。
#include <bits/stdc++.h> using namespace std; int T,N; int a[1000005]; int main(){ scanf("%d",&T); while (T--){ scanf("%d",&N); for (int i=0;i<=N;i++){ scanf("%d",&a[i]); } for (int i=N;i>=1;i--) a[i-1]+=a[i]/2; if (a[0] != 0) printf("Alice\n"); else printf("Bob\n"); } return 0; }
赛后补题:
C
血压暴涨题
考虑表示前个物品,和为,物品重量为
^
然后发现最后一维可以用压掉,第一维滚动数组。
因为没有hdoj的号所以数据是手测的
//dp[i][k][j] = dp[i-1][k^w[i]][j-a[i]] | dp[i-1][k][j] #include <bits/stdc++.h> using namespace std; bitset<1060> dp[2][1060]; int v[1060],w[1060]; int main(){ freopen("1003.in","r",stdin); freopen("1003.out","w",stdout); int T; scanf("%d",&T); while (T--){ int N,M; scanf("%d%d",&N,&M); for (int i=0;i<=1;i++) for (int j=0;j<=1024;j++) dp[i][j].reset(); for (int i=1;i<=N;i++) scanf("%d%d",&v[i],&w[i]); dp[0][0]=1; for (int i=1;i<=N;i++) for (int j=0;j<1024;j++){ int o=i&1; dp[o][j] = dp[o^1][j]; dp[o][j] |= (dp[o^1][j^w[i]])<<v[i]; } int ans=-1; for (int i=0;i<1024;i++){ if (dp[N&1][i][M]) ans=i; } printf("%d\n",ans); } }
7.23 牛客
队友都有事,我也有事,自己一个人打了三小时就下班了
G
构造题
构造方式是把到序列分成段
每一段内逆序
然后得到的长度就是
显然
#include <bits/stdc++.h> using namespace std; int N; int T; int main(){ scanf("%d",&T); while (T--){ scanf("%d",&N); int Len =int((sqrt(N))+0.5); for (int i=0;i<N;i+=Len){ if (i+Len-1 < N){ for (int j=i+Len-1;j>=i;j--) printf("%d ",j+1); } else{ for (int j=N-1;j>=i;j--){ printf("%d ",j+1); } } } printf("\n"); } return 0; }
J
线性回归方程
套式子就好了。
有点卡精度,自然数的和拆开一下,用
#include <bits/stdc++.h> using namespace std; int T; long double Pow(long double x){ return x*x; } long double a[100006]; int main(){ cin>>T; while (T--){ long double N; scanf("%Lf",&N); long double ans=0; long double XY=0,SumX=0,SumY=0,SumX2=0; for (long double i=1;i<=N;i++){ int x=i; scanf("%Lf",&a[x]); XY+=a[x]*i; SumX+=i; SumY+=a[x]; SumX2+=i*i; } long double SumXX = SumX*SumX; long double AverY = SumY/N; long double b = (XY - (N+1)/2*SumY)/(N*(N+1)*(2*N+1)/6-(N+1)*(N+1)*N/4); long double aa = AverY - b*(N+1)/2; for (int i=1;i<=N;i++){ ans = ans + Pow(a[i] - (b*i+aa)); } printf("%.15Lf\n",ans); } return 0; }
K
(视为,视为
串考虑前位,为,前缀和为
转移随便推推就出来了
答案在里
#include <bits/stdc++.h> using namespace std; int dp[205][205][205]; const int fish = 1e9+7; int main(){ int T; cin>>T; while (T--){ int N,M; string ss; scanf("%d%d",&N,&M); cin>>ss; for (int i=1;i<=M;i++) for (int j=0;j<=N;j++) for (int k=0;k<=100;k++) dp[i][j][k] = 0; dp[0][0][0] = 1; for (int i=1;i<=M;i++) for (int j=0;j<=N;j++) for (int k=0;k<=100;k++){ if (j == 0){ dp[i][j][k] = dp[i-1][j][k+1]%fish; if (k>0) (dp[i][j][k]+=dp[i-1][j][k-1])%=fish; } if (j!=0&&ss[j-1] == '('){ dp[i][j][k] = dp[i-1][j][k+1]%fish; if (k-1 >= 0 && j-1>=0) (dp[i][j][k]+=dp[i-1][j-1][k-1])%=fish; } else if(j!=0) { if (k-1 >=0) dp[i][j][k] = dp[i-1][j][k-1]%fish; if (j-1 >= 0)(dp[i][j][k]+=dp[i-1][j-1][k+1])%=fish; } //cout<<i<<" "<<j<<" "<<k<<" "<<dp[i][j][k]<<endl; } printf("%d\n",dp[M][N][0]%fish); } return 0; }
补题:
L
时间不够根本没看,结果其实是简单题(
首先题目要求连续段,肯定是考虑这个连续段的左右端点
不妨设表示在号世界,能到达第个点的最右左端点
转移的话就两种,一种情况是不动,一种情况是走边
但是需要注意的是,号点无论在上一个世界还是这一个世界,都视为在第个世界
//dp[i][j]表示考虑到第i个世界,当前在j号点,最晚从哪个地方开始 //dp[i][y] = max{dp[i-1][y],dp[i-1][x]} x->y 存在 //ans = max i-dp[i][T]+1 #include <bits/stdc++.h> using namespace std; int dp[2][10005]; int N,M; int ans=1e9+7; int main(){ scanf("%d%d",&N,&M); for (int i=1;i<=N;i++){ int L; int nw = i&1; int lst =1-i&1; scanf("%d",&L); dp[nw][1] = i; dp[lst][1] = i; for (int j=2;j<=N;j++) dp[nw][j] = dp[lst][j]; for (int i=1;i<=L;i++){ int x,y; scanf("%d%d",&x,&y); dp[nw][y] = max(dp[nw][y] , dp[lst][x]); } if (dp[nw][M]!=0) ans = min(ans,i-dp[nw][M]+1); } if (ans == 1e9+7) cout<<-1; else cout<<ans; return 0; }
C
读懂题目,然后二分+SPFA找个环就好了
#include <bits/stdc++.h> using namespace std; int N,M; int cnt,las[10005],nex[10005],Arrive[10005]; long double dis[10005],qz1[10005],qz[10005]; void Add(int x,int y,long double v){ cnt++; nex[cnt]=las[x]; las[x]=cnt; Arrive[cnt]=y; qz[cnt]=v; } int Times[10005]; bool vis[10005]; bool Check(long double K){ for (int i=1;i<=M;i++) qz1[i] = qz[i] *K; queue<int> SP; memset(Times,0,sizeof(Times)); for (int i=1;i<=N;i++){ SP.push(i); dis[i]=1; vis[i]=true; } while (!SP.empty()){ int nw = SP.front(); SP.pop(); if (Times[nw] > N) return true; vis[nw] = false; for (int i=las[nw];i;i=nex[i]) if (dis[Arrive[i]] < dis[nw] * qz1[i]){ dis[Arrive[i]] =dis[nw] * qz1[i]; Times[Arrive[i]] =Times[nw] +1; if (!vis[Arrive[i]]){ SP.push(Arrive[i]); vis[Arrive[i]] = true; } } } return false; } int main(){ scanf("%d%d",&N,&M); for (int i=1;i<=M;i++){ int b,d; long double a,c; scanf("%Lf%d%Lf%d",&a,&b,&c,&d); Add(b,d,1.0*c/a); } long double l=0,r=1; while (r-l>1e-9){ double mid=(l+r)/2; if (Check(mid)) r=mid; else l=mid; } printf("%.10Lf\n",l); return 0; }
7.25牛客
不会SAM输麻了
A
实际上考虑删除什么点会导致变化
一定是某个点,它下面挂着两个子树,一个有个关键点,另一个有一个关键点,或者本身是一个关键点
然后两个树分别跑一遍去找这些能删的点就好了。
具体看代码(
复杂度
实际上有个复杂度带一个但代码非常好写的做法(
#include <bits/stdc++.h> using namespace std; int N,K; int va[100005],vb[100005]; vector<int> A[100005],B[100005]; bool flaglca; int Son[100005]; bool flaga,flagb; bool flag1=false; int wha[100005],whb[100005],lcaA,lcaB,asA,asB,WhoA,WhoB,flaglcb,x[100005]; bool pda[100005],pdb[100005]; void dfs1(int Now,int fa){ bool pd1=false,pd2=false; if (pda[Now]) { Son[Now] = 1;wha[Now] = Now;} for (auto Arr:A[Now]){ dfs1(Arr,Now); Son[Now] += Son[Arr]; if (Son[Arr] == 1) wha[Now] = wha[Arr]; if (Son[Arr] == K-1) pd1=true; if (Son[Arr] == 1) pd2=true; } if (Son[Now] == K && !flaglca){ flaglca=true; lcaA=Now; } if (Son[Now] == K-1 && !flag1) { flag1=true; asA=Now; } if (pd1 && pd2 && !flagb) { flagb=true; WhoA=wha[Now]; } if (!flagb&&pd1 && pda[Now]){ flagb=true; WhoA=Now; } } void dfs2(int Now,int fa){ bool pd1=false,pd2=false; if (pdb[Now]) { Son[Now] = 1;whb[Now] = Now;} for (auto Arr:B[Now]){ dfs2(Arr,Now); Son[Now] += Son[Arr]; if (Son[Arr] == 1) whb[Now] = whb[Arr]; if (Son[Arr] == K-1) pd1=true; if (Son[Arr] == 1) pd2=true; } if (Son[Now] == K && !flaglcb){ flaglcb=true; lcaB=Now; } if (Son[Now] == K-1 && !flag1) { flag1=true; asB=Now; } if (pd1 && pd2 && !flagb) { flagb=true; WhoB=whb[Now]; } if (!flagb&&pd1 && pdb[Now]){ flagb=true; WhoB=Now; } } int main(){ scanf("%d%d",&N,&K); for (int i=1;i<=K;i++){ scanf("%d",&x[i]); pda[x[i]]=true; pdb[x[i]]=true; } for (int i=1;i<=N;i++) scanf("%d",&va[i]); for (int i=2;i<=N;i++){ int fa; scanf("%d",&fa); A[fa].push_back(i); } for (int i=1;i<=N;i++) scanf("%d",&vb[i]); for (int i=2;i<=N;i++){ int fa; scanf("%d",&fa); B[fa].push_back(i); } if (K==2){ int ans=0; if (va[x[1]]>vb[x[1]]) ans++; if (va[x[2]]>vb[x[2]]) ans++; printf("%d\n",ans); return 0; } dfs1(1,1); memset(Son,0,sizeof(Son)); flag1=false; flagb=false; dfs2(1,1); int ans=0; if (WhoA == 0 && WhoA == 0){ if (va[lcaA] > vb[lcaB]) printf("%d\n",K); else printf("%d\n",0); return 0; } if (WhoA == WhoB){ if (WhoA!=0){ if (va[lcaA]>vb[lcaB]) ans += K-1; if (va[asA] > vb[asB]) ans++; cout<<ans; return 0; } else if (va[lcaA]>vb[lcaB]) printf("%d\n",K);else printf("0\n"); return 0; } //cout<<"qwq"<<endl; if (WhoA != WhoB){ if (va[lcaA] > vb[lcaB]) ans += K-2; if (WhoA!=0){ if (va[asA]>vb[lcaB]) ans++; } else if (WhoA==0&&va[lcaA] > vb[lcaB]) ans++; if (WhoB!=0){ if (va[lcaA]>vb[asB]) ans++; }else if (WhoB == 00&&va[lcaA] > vb[lcaB]) ans++; cout<<ans; return 0; } return 0; }
C
它的本意大概是想让人写个然后线性吧
但是它没卡成一个的做法
所以直接排序即可(
#include <bits/stdc++.h> using namespace std; string ss[2000005]; bool temp(string s1,string s2){ if (s1.length()==s2.length()) return s1<s2; else { string ss3=s1+s2,ss4=s2+s1; return ss3<ss4; } } int main(){ int N; scanf("%d",&N); for (int i=1;i<=N;i++){ cin>>ss[i]; } sort(ss+1,ss+N+1,temp); for (int i=1;i<=N;i++) cout<<ss[i]; return 0; }
J
讲完思路扔给队友了不过队友没写完,不过也没差了(
做法大概是考虑把路当成点,然后在路口考虑方向跑一个
但是这个因为有右转贡献的存在,所以要开双端队列
代码等队友补完再说吧(
补题
H
不会输麻了…
考虑题目在说个什么东西
其实本质上就是求,在是的公共子串的前提下的最大子段和
而每个位置的公共子串长度是经典的问题,把串建个,把串扔上去跑,能跳就跳,不能跳就删除首字符继续跳。
然后最大子段和问题可以用单调队列处理
然后就结束了
当个板子记录吧(
#include <bits/stdc++.h> #define ll long long using namespace std; int N,M,K; string ss; int lst,tot,len[300005],fa[300005],tr[300005][28]; void Extend(int nw){ int x = ++tot,u=lst,v; lst = tot; len[x] = len[u] + 1; for (;u && !tr[u][nw]; tr[u][nw] = x,u = fa[u]); if (!u) fa[x] = 1; else if (len[u]+1 == len[v = tr[u][nw]]) fa[x] = v; else{ int w = ++tot; len[w] = len[u]+1,fa[w] = fa[v]; memcpy(tr[w],tr[v],sizeof(tr[v])); for (fa[v] = fa[x] = w ; u && tr[u][nw] == v ; tr[u][nw]=w,u=fa[u]); } } ll Sum[300005]; int stk[300005]; void Solve(){ char ss[100005]; cin>>ss+1; int Len = M; int nw = 1,L = 0; int h=0,t=0; int x=1; stk[0] = 0; ll ans = 0; for (int i=1;i<=Len;i++){ while (x && !tr[x][ss[i] - 'a']) {x = fa[x],L = len[x];} if (!x) x=1,L=0; else x = tr[x][ss[i]-'a'],++L; while(h<=t && Sum[stk[t]] >= Sum[i]) t--; stk[++t] = i; while (h<=t && stk[h]< i - L) h++; ans = max(ans,Sum[i] - Sum[stk[h]]); } printf("%lld\n",ans); } int main(){ scanf("%d%d%d",&N,&M,&K); string ss; cin>>ss; tot = lst = 1; int Len = ss.length(); for (int i = 0 ;i< Len; i++) Extend(ss[i] - 'a'); for (int i = 1;i<=M;i++){ int x; scanf("%d",&x); Sum[i] = Sum[i-1]+x; } for (int i=1;i<=K;i++){ Solve(); } }
7.26 杭电
1002
二分出被击败的时间,然后里面直接写一个状压即可
因为二分完时间后就知道每个技能能造成多少伤害了。
#include <bits/stdc++.h> #define endl '\n' using namespace std; typedef long long ll; const int N = 1e5 + 5; const int INF = 2e6; int n, t[N], len[N], tt[N * 3]; ll H, d[20][N], dp[N * 3]; bool check(int x) { for (int i = 1; i < (1 << n); ++i) { dp[i] = 0; for (int j = 1; (1 << (j - 1)) < (1 << n); j++) { if ((1 << (j - 1)) & i) { // subset int k = i ^ (1 << (j - 1)); tt[i] = tt[k] + t[j]; if (tt[k] >= x) continue; dp[i] = max(dp[i], dp[k] + d[j][min(len[j], x - tt[k])]); // cerr << i << " " << j << " " << len[j] << " " << x << " " << tt << " " << min(len[j] - 1, x - tt) << endl; } } if (dp[i] >= H) return true; } // for (int i = 1; i < (1 << n); ++i) // cout << dp[i] << " "; // cout << endl; return false; } void solve() { cin >> n >> H; for (int i = 1; i <= n; ++i) { cin >> t[i] >> len[i]; for (int j = 1; j <= len[i]; ++j) { cin >> d[i][j]; d[i][j] += d[i][j - 1]; } } int l = 0, r = INF; while (l < r) { int mid = (l + r) >> 1; if (check(mid)) r = mid; else l = mid + 1; } cout << (l == INF ? -1 : l - 1) << endl; } int main() { ios::sync_with_stdio(false); cin.tie(0); int T; cin >> T; while (T--) solve(); return 0; }
1003
签到题
#include <iostream> #include <string> #include <cstring> #include <cstdio> using namespace std; int main() { int t; cin >> t; char c=getchar(); char str[100024]; while(t--) { cin.get(str,100024); cin.get(); printf("%c",(char)str[0]-'a'+'A'); for(int i=1;i<strlen(str)-1;i++) if(str[i-1]==' ') printf("%c",(char)str[i]-'a'+'A'); cout << endl; } return 0; }
1009
有个显然的贪心,能不取则不取,直到一定要取
拿个维护这个过程就好了。
#include <bits/stdc++.h> using namespace std; struct Node{ int l,r; }a[100005]; int N,K; bool temp(Node a,Node b){ return ((a.l<b.l)||(a.l == b.l&&a.r<b.r)); } set<int> Q; void Solve(){ scanf("%d%d",&N,&K); for (int i=1;i<=N;i++){ int x,y; scanf("%d%d",&x,&y); a[i]={x,y}; } sort(a+1,a+N+1,temp); int ans=0; for (int i=1;i<=N;i++){ while (!Q.empty() && *Q.begin()<a[i].l){ int Times=K; while (Times--) if (!Q.empty()){ Q.erase(Q.begin()); } ans++; } Q.insert(a[i].r); } while (!Q.empty()){ int Times=K; while (Times--) if (!Q.empty()){ Q.erase(Q.begin()); } ans++; } printf("%d\n",ans); Q.clear(); } int main(){ int T; scanf("%d",&T); while (T--){ Solve(); } return 0; }
1012
表示中前个数,匹配到了的方案数
然后通过这个转移会发现其实每次有用的状态只有两个
一遍就好。
#include <bits/stdc++.h> #define endl '\n' using namespace std; typedef long long ll; const int N = 3e5 + 5; const int INF = 0x3f3f3f3f; const int MOD = 998244353; int n, P[N], Q[N], S[N * 2], t[N]; ll dp[2][N]; void solve() { cin >> n; for (int i = 0; i <= n; ++i) dp[0][i] = dp[1][i] = t[i] = 0; for (int i = 1; i <= n; ++i) { int p; cin >> p; P[p] = i; } for (int i = 1; i <= n; ++i) { int q; cin >> q; Q[q] = i; } for (int i = 1; i <= n * 2; ++i) cin >> S[i]; int o = 1; dp[0][0] = 1; for (int i = 1; i <= n * 2; ++i, o ^= 1) { // for (int j = 0; j <= n; ++j) // dp[o][j] = 0; // dp[o][P[S[i]]] = (dp[o ^ 1][P[S[i]] - 1] + dp[o ^ 1][i - Q[S[i]]]) % MOD; dp[o][P[S[i]]] = 0; if (i - Q[S[i]] <= n && i - Q[S[i]] >= 0) dp[o][i - Q[S[i]]] = 0; if (t[P[S[i]] - 1] == i - 1) dp[o][P[S[i]]] += dp[o ^ 1][P[S[i]] - 1]; if (i - Q[S[i]] >= 0 && i - Q[S[i]] <= n && t[i - Q[S[i]]] == i - 1) dp[o][i - Q[S[i]]] += dp[o ^ 1][i - Q[S[i]]]; dp[o][P[S[i]]] %= MOD; if (i - Q[S[i]] <= n && i - Q[S[i]] >= 0) dp[o][i - Q[S[i]]] %= MOD; t[P[S[i]]] = i; if (i - Q[S[i]] <= n && i - Q[S[i]] >= 0) t[i - Q[S[i]]] = i; } if (t[n] == n * 2) cout << dp[o ^ 1][n] % MOD << endl; else cout << 0 << endl; } int main() { ios::sync_with_stdio(false); cin.tie(0); int T; cin >> T; while (T--) solve(); return 0; }
1011
犯蠢大题(
首先把曼哈顿距离转为切比雪夫距离
然后考虑二分答案
二分的时候,考虑以答案的时候,需要满足的是:
也就意味着,切比雪夫距离和距离都要比小
不妨假设我们现在二分一个,让它作为
然后这题就变成,考虑所有权比大的数,是否都存在比大的距离
那么我们显然只要考虑四个点,即切比雪夫坐标下后缀最左,最右,最上,最下的点与这个点的距离。这个一个后缀和就搞定了。
然后我们每次二分,更新答案,然后如果没有就往小了找,有就往大了找。
#include <bits/stdc++.h> using namespace std; int T,N,Q; int ans=0; int w1[300005],Mnx[300005],Mxx[300005],Mxy[300005],Mny[300005]; int x[300005],y[300005],w[300005]; int Check(int x,int X,int Y){ bool flag=false; int anss=-2e9; anss = max(anss,min(w1[x],abs(Mxx[x]-X))); anss = max(anss,min(w1[x],abs(Mxy[x]-Y))); anss = max(anss,min(w1[x],abs(Y-Mny[x]))); anss = max(anss,min(w1[x],abs(X-Mnx[x]))); ans=max(ans,anss); if (abs(Mxx[x]-X) >= w1[x]) flag=true; //if (x==0) cout<<anss<<" "<<"qwq"<<endl; if (abs(Mxy[x]-Y)>= w1[x]) flag=true; //if (x==0) cout<<anss<<" "<<"qwqq"<<" "<<Mxy[x]-Y<<endl; if (abs(Y-Mny[x]) >= w1[x]) flag=true; if (abs(X - Mnx[x]) >= w1[x])flag=true; //if (x==0) cout<<min(w1[x],Y-Mny[x])<<endl; if (flag) return anss; else return -1; } int main(){ //freopen("1011.in","r",stdin); //freopen("data.out","w",stdout); scanf("%d",&T); while (T--){ scanf("%d%d",&N,&Q); int NN=N; for (int i=1;i<=N;i++){ scanf("%d%d%d",&x[i],&y[i],&w[i]); int nx,ny; nx=x[i]+y[i]; ny=x[i]-y[i]; x[i]=nx,y[i]=ny; } for (int i=1;i<=N;i++) w1[i]=w[i]; sort(w1+1,w1+N+1); N=unique(w1+1,w1+N+1)-w1-1; for (int i=0;i<=N;i++) Mnx[i]=Mny[i]=2e9+7,Mxx[i]=Mxy[i]=-2e9; for (int i=1;i<=NN;i++){ int pos = lower_bound(w1+1,w1+N+1,w[i])-w1; Mxx[pos] = max(Mxx[pos],x[i]); Mxy[pos] = max(Mxy[pos],y[i]); Mnx[pos] = min(Mnx[pos],x[i]); Mny[pos] = min(Mny[pos],y[i]); } for (int i=N-1;i>=0;i--){ int pos=i; Mxx[pos]=max(Mxx[pos],Mxx[pos+1]); Mxy[pos]=max(Mxy[pos],Mxy[pos+1]); Mny[pos]=min(Mny[pos],Mny[pos+1]); Mnx[pos]=min(Mnx[pos],Mnx[pos+1]); } //cout<<Mnx[0]<<endl; w1[0] = 2e9+7; while (Q--){ int l=1,r=N,xx,yy; scanf("%d%d",&xx,&yy); int X,Y; X=xx+yy; Y=xx-yy; xx=X,yy=Y; while (l<=r){ //cout<<mid<<endl; int mid = (l+r)>>1; if (Check(mid,xx,yy)!=-1) ans=max(ans,Check(mid,xx,yy)),l=mid+1; else r=mid-1; } printf("%d\n",ans); } } }
补题:
1008
感性理解一下,最后答案平面一定是由所有线段的某三个端点决定的,所以枚举三个端点暴力判断即可。
记得判断一下共线的情况
判断线段与平面关系用混合积,判断两端点和平面的位置关系
#include <bits/stdc++.h> using namespace std; struct Node { int x,y,z; Node(){} Node(int a,int b,int c){x=a,y=b,z=c;} Node operator - (const Node &p) const{return Node(x-p.x,y-p.y,z-p.z);} Node operator * (const Node &p) const{return Node(y*p.z-z*p.y,z*p.x-x*p.z,x*p.y-y*p.x);} int operator ^ (const Node &p) const{return x*p.x+y*p.y+z*p.z;} bool operator == (const Node &p) const {return (x==p.x && y==p.y && z==p.z);} }a[505]; void output(Node nw){ cout<<nw.x<<" "<<nw.y<<" "<<nw.z<<endl; } bool CoLine(Node a,Node b,Node c){ Node nw = (c-a)*(c-b); return (nw.x==0 &&nw.y ==0 && nw.z==0); } int GetSide(Node a,Node b,Node c,Node d){ return ((b-a)*(c-a))^(d-a);//混合积 } int main(){ int T; scanf("%d",&T); while (T--){ int ans=0; int N; scanf("%d",&N); for (int i=0;i<2*N;i++){ int x,y,z; scanf("%d%d%d",&x,&y,&z); a[i].x=x; a[i].y=y; a[i].z=z; } for (int i=0;i<2*N;i++) for (int j=0;j<i;j++){ if (a[i] == a[j]) continue; for (int k=0;k<j;k++){//面 if (i==4&& j==1 && k==0 && a[i] == a[k]) { output(a[i]);output(a[k]); } if (a[j] == a[k] || a[i] == a[k]) continue; if (CoLine(a[i],a[j],a[k])) continue; int nww=0; for (int nw=0;nw<N;nw++){ int x = GetSide(a[i],a[j],a[k],a[nw<<1]); int y = GetSide(a[i],a[j],a[k],a[nw<<1|1]); if (x==0 || y==0 ||1ll*x*y<0) nww++; } ans=max(nww,ans); } int nww=0; for (int k=0;k<N;k++) if (CoLine(a[i],a[j],a[k<<1])|| CoLine(a[i],a[j],a[k<<1|1])) nww++; ans=max(ans,nww); } printf("%d\n",ans); } return 0; }
7.28 杭电
1001
区间dp,写dp的队友现场有点事情,回头补(
1002
图论
显然考虑先扫一遍把跑出来,然后转移方向就确定了
然后在新图上跑个最长路就好了。
环问题用缩点解决。
代码还没写完(
1004
签到,输出一串
打表或者具体证明都还行
#include <bits/stdc++.h> using namespace std; int main(){ int T,N; scanf("%d",&T); while(T--){ int N; scanf("%d",&N); printf("No\n"); } return 0; }
1006
签到,阅读理解读清楚就好。
#include <bits/stdc++.h> using namespace std; double ans1,ans2; int N; int a[100005]; int main(){ int T; cin>>T; while (T--){ ans1=ans2=0; scanf("%d",&N); for (int i=1;i<=N;i++){ scanf("%d",&a[i]); if (ans1 <=100 && ans1 + a[i]>=100){ double xx = ans1+a[i]-100; if (ans1 + xx *0.8 <=200) ans1=100+xx*0.8; else{ double Ned = (100 - ans1); double xxx = a[i] - Ned - 125; ans1 = ans1 + Ned + 100 + xxx*0.5; } }else if (ans1 < 200 && ans1>=100 && ans1 + 0.8*a[i] >=200){ double Ned = 200-ans1; double xxx = Ned / 0.8; double nw = a[i] - xxx; ans1 = 200 + nw*0.5; }else if (ans1<200 && ans1>=100){ ans1 = ans1 + a[i] *0.8; } else if (ans1 >= 200) ans1 = ans1 + a[i]*0.5; else if (ans1 <=100) ans1 = ans1 + a[i]; if (ans2<200 && ans2 >=100) ans2 = ans2 + a[i] * 0.8;else if (ans2 >=200) ans2 = ans2 + a[i] * 0.5;else if (ans2 <100) ans2 = ans2 + a[i]; //cout<<ans1<<endl; } printf("%.3lf %.3lf\n",ans1,ans2); } return 0; }
1007
出题人锐评:比较签(
考虑题目的本质,其实一定是往前跳一次,然后不断地一步一步往回跳
然后题目其实就转化为把序列分成一堆段,然后相邻两段之间的长度和
写一下式子,假设当前这段从开始,当前攻击力为,到结束
那么对每一个和之间的就要满足
移项
取最大的,枚举,已知
能取就直接划一刀,因为这样能给下一段留出更多的长度空间。
#include <bits/stdc++.h> using namespace std; double ans1,ans2; int N; int a[100005]; int main(){ int T; cin>>T; while (T--){ ans1=ans2=0; scanf("%d",&N); for (int i=1;i<=N;i++){ scanf("%d",&a[i]); if (ans1 <=100 && ans1 + a[i]>=100){ double xx = ans1+a[i]-100; if (ans1 + xx *0.8 <=200) ans1=100+xx*0.8; else{ double Ned = (100 - ans1); double xxx = a[i] - Ned - 125; ans1 = ans1 + Ned + 100 + xxx*0.5; } }else if (ans1 < 200 && ans1>=100 && ans1 + 0.8*a[i] >=200){ double Ned = 200-ans1; double xxx = Ned / 0.8; double nw = a[i] - xxx; ans1 = 200 + nw*0.5; }else if (ans1<200 && ans1>=100){ ans1 = ans1 + a[i] *0.8; } else if (ans1 >= 200) ans1 = ans1 + a[i]*0.5; else if (ans1 <=100) ans1 = ans1 + a[i]; if (ans2<200 && ans2 >=100) ans2 = ans2 + a[i] * 0.8;else if (ans2 >=200) ans2 = ans2 + a[i] * 0.5;else if (ans2 <100) ans2 = ans2 + a[i]; //cout<<ans1<<endl; } printf("%.3lf %.3lf\n",ans1,ans2); } return 0; }
1011
关于写了一大堆结果我其实只推了其中一部分就莽了上去这件事
首先,对于一个长度为偶数的区间来说,做两次操作一定能把区间全变成
然后其实我们每次可以取两个,做两次,得到两个0
然后就会发现,我们可以选择保留一部分数字,或者不保留一部分数字
因为的出现可以使数字的位置进行更换
于是问题就变成了取这些数字中的一部分,让它们最大。
然后差点不会了(
这个东西套一个线性基就好了。
#include <bits/stdc++.h> using namespace std; int T,N; #define ll long long long long d[65]; void add(ll x) { for(int i=60;i>=0;i--) { if(x&(1ll<<i)) { if(d[i])x^=d[i]; else { d[i]=x; break; } } } } ll ans() { ll anss=0; for(int i=60;i>=0;i--) if((anss^d[i])>anss)anss^=d[i]; return anss; } int main(){ scanf("%d",&T); while (T--){ memset(d,0,sizeof(d)); scanf("%d",&N); for (int i=1;i<=N;i++){ long long x; scanf("%lld",&x); add(x); } printf("%lld\n",ans()); } }
赛后补题
1003
实际上读懂题目之后,把不等式一写就是差分约束板题了(
#include<bits/stdc++.h> using namespace std; #define inf 1e9+7 int cnt,las[30005],dis[30005],Times[30005],nex[30005],Arrive[30005],qz[30005]; bool Is[30005]; void Clear(int mx){ cnt=0; for (int i=1;i<=mx;i++) las[i]=0; } void jt(int x,int y,int val){ cnt++; nex[cnt]=las[x]; las[x]=cnt; Arrive[cnt]=y; qz[cnt]=val; } bool Loop=0; void SPFA(int S,int NN){ deque<int> Bfs; for (int i=1;i<=NN;i++){ dis[i] = inf; Is[i]=false; Times[i]=0; } Bfs.push_front(S); dis[S]=0; Is[S]=true; Times[S]=1; while (!Bfs.empty()){ int nw = Bfs.front(); Bfs.pop_front(); // cout<<nw<<endl; for (int i=las[nw];i;i=nex[i]){ int v=Arrive[i]; if (dis[v] > dis[nw] + qz[i] && dis[nw] != inf){ dis[v] = dis[nw] + qz[i]; if (!Is[v]){ Is[v]=true; if (!Bfs.empty() && dis[v] < dis[Bfs.front()]){ Bfs.push_front(v); Times[v] ++; if (Times[v] == NN) { Loop = -1; return; } } else{ Bfs.push_back(v); Times[v] ++; if (Times[v] == NN) { Loop = -1; return; } } } } } Is[nw] = false; } } int main(){ int T; scanf("%d",&T); while (T--){ int N,K; Loop=0; scanf("%d%d",&N,&K); for (int i=1;i<=N;i++){ int x; scanf("%d",&x); int S = max(i-K,0); if (S==0) S=N+1; int T= min(i+K-1,N); jt(T,S,-x); //cout<<T<<" "<<S<<endl; } for (int i=2;i<=N;i++){ jt(i,i-1,0); } jt(1,N+1,0); int Q; scanf("%d",&Q); while (Q--){ int x,y,z; scanf("%d%d%d",&x,&y,&z); int S=x-1; if (x-1 <=0) S=N+1; jt(S,y,z); } SPFA(N,N+1); if (Loop) printf("-1\n"); else printf("%d\n",-dis[N+1]); Clear(N+5); } return 0; }
1005
对顶栈有点妙发现范围很小,可以直接用矩阵存一下方案数转移
然后发现区间的都是单调的
可以双指针
但是有问题,矩阵的逆不一定存在,所以不能除
所以要规避除法,有两个做法
1、线段树直接查区间矩阵乘积,不知道会不会被卡
2、对顶栈
细说一下对顶栈
把一个区间拆成和两个部分
对于左边那个部分,每个点都存一个后缀的矩阵乘
对于右边那个部分,每次就直接乘上当前位置的矩阵
此时,我们就会发现,左边是一个只会出栈的栈,右边是一个只会入栈的栈。
每次,在左边的栈空掉之后,我们就把右边的栈全部,然后压进左边这个栈
通过这种方法,我们就可以直接通过查询左边的栈到栈顶的乘积,以及右边所有元素的乘积来查询
然后,其实每个元素只会在右边的栈进出一次,左边一次
所以是线性的。
#include <bits/stdc++.h> using namespace std; const long long INF = 1e9; int N,M,K; int limit(long long x){ if(x>=INF) return INF; else return x; } struct Matrix{ long long A[55][55]; Matrix operator * (Matrix B) const{ Matrix New; for (int i = 1;i<=M;i++) for (int j = 1; j <= M; j++) New.A[i][j] = 0; for (int i = 1 ; i <= M ; i++) for (int j = 1 ;j <= M ; j++) for (int k = 1 ; k <= M ;k++){ New.A[i][j] = limit(New.A[i][j]+(long long)A[i][k]*B.A[k][j]); } return New; } void Output(){ cout<<"qwq"<<endl; for (int i = 1;i<=M;i++){ for (int j = 1;j<=M;j++){ cout<<A[i][j]<<" "; } cout<<endl; } cout<<"qwq"<<endl; } void setOne(){ for (int i = 1; i <= M ; i ++) for (int j =1 ; j <= M ; j ++) A[i][j] = 0; for (int i = 1 ; i <= M ; i ++) A[i][i]=1; } }d[5005]; bool Check(Matrix a,Matrix b){ long long ans = 0; for (int i = 1;i<= M;i++){ ans = min(INF,ans + 1ll*a.A[1][i]*1ll*b.A[i][M]); } //cout<<ans<<endl; return ans <= K; } int cnt = 0; int T,TT; Matrix stk[5005]; void Solve(){ scanf("%d%d%d",&N,&M,&K); for (int i = 1 ;i<= N ;i ++ ) d[i].setOne(); for (int i = 1 ;i<=N;i++){ int l; scanf("%d",&l); while (l--){ int u,v; scanf("%d%d",&u,&v); d[i].A[u][v] = 1; } } Matrix nw; nw.setOne(); int ans = 0 ; Matrix Other; Other.setOne(); stk[0].A[1][M] = INF; for (int l=0,r=1,lim=0;r<=N;r++){ Other = Other * d[r]; while (!Check(stk[l],Other)){ l++; if (l>lim){ stk[r] = d[r]; for (int i = r-1 ; i > lim;i--) stk[i] = d[i] * stk[i+1] ; Other.setOne(); lim = r; } } ans = max(ans,r-l+1); } printf("%d\n",ans); } int main(){ scanf("%d",&T); TT = T; while (T--){ cnt++; Solve(); } return 0; }
7.30牛客
D
前缀一下就好了
注意读入格式。
#include <bits/stdc++.h> using namespace std; #define ll long long int n,q,m; const ll p=998244353; ll seed; inline int gi(){ int x = 0, f = 1, c = getchar(); for(; !isdigit(c); c = getchar()) f = c=='-'?-1:1; for(; isdigit(c); c = getchar()) x = x*10 + (c^48); return x * f; } int Min[11][405][405]; ll anss=0; ll poww(ll a, ll b){ ll ans = 1, base = a; while (b){ if (b&1) ans=1ll*ans*base%p; base = 1ll*base*base%p; b>>=1; } return ans; } ll solve(int IQ,int EQ, int AQ) { ll ans=0; //printf("%d %d %d\n", IQ, EQ, AQ); //IQ--, EQ--, AQ--; for (int i = 1; i <= n; i++) if (Min[i][IQ][EQ] <= AQ) ans++; //cout <<"ans="<< ans << endl; return ans; } void init() { cin >> n >> q; memset(Min, 0x3f, sizeof(Min)); for(int i=1;i<=n;i++) { m=gi(); // ==m[i] for(int j=1;j<=m;j++) { int a=gi(),b=gi(),c=gi(); if (Min[i][a][b] > c) Min[i][a][b] = c; } for (int j = 1; j <= 400; j++) for (int k = 1; k <= 400; k++) { if (Min[i][j][k] > Min[i][j - 1][k]) Min[i][j][k] = Min[i][j - 1][k]; if (Min[i][j][k] > Min[i][j][k - 1]) Min[i][j][k] = Min[i][j][k - 1]; } } cin >> seed; } int main() { init(); std::mt19937 rng(seed); std::uniform_int_distribution<> u(1,400); ll lastans=0; for (int i=1;i<=q;i++) { int IQ=(u(rng)^lastans)%400+1; // The IQ of the i-th friend int EQ=(u(rng)^lastans)%400+1; // The EQ of the i-th friend int AQ=(u(rng)^lastans)%400+1; // The AQ of the i-th friend // int IQ,EQ,AQ; // if(i==1) IQ=92,EQ=108,AQ=303; // if(i==2) IQ=116,EQ=36,AQ=265; // if(i==3) IQ=255,EQ=132,AQ=185; // if(i==4) IQ=360,EQ=219,AQ=272; // if(i==5) IQ=8,EQ=115,AQ=254; lastans=solve(IQ,EQ,AQ); // The answer to the i-th friend anss=(anss+(ll)lastans*poww(seed,q-i)%p)%p; } cout << anss << endl; return 0; }
H
枚举长宽,暴力然后暴力输出
填的话就贪心的填就好了。
#include <bits/stdc++.h> using namespace std; inline int gi(){ int x = 0, f = 1, c = getchar(); for(; !isdigit(c); c = getchar()) f = c=='-'?-1:1; for(; isdigit(c); c = getchar()) x = x*10 + (c^48); return x * f; } #define ll long long int b[110]; void print(int x,int y,int n) { for(int i=1;i<=n;i++) b[i]=n-i+1; //cout << " x "<<x<<" y "<<y<<endl; for(int i=1;i<=y;i++) { int t=x; while(t){ for(int j=n;j>=1;j--) while(b[j]&&j<=t) { b[j]--; printf("%d %d %d %d\n",x-t,i-1,x-t+j,i); t-=j; } } } } bool ok(int x,int y, int n) { for(int i=1;i<=n;i++) b[i]=n-i+1; for(int i=1;i<=y;i++) { int t=x; while(t){ int flag=0; for(int j=n;j>=1;j--) { //cout<<i<<" "<<t<<" "<<j<<endl; while(b[j]&&j<=t) { b[j]--; t-=j; flag=1; } } if(!flag) return false; } } return true; } void solve(int n) { int tot=0; for(int i=1;i<=n;i++) tot+=i*(n-i+1); int ansx=-1,minc=1e9+7; for(int x=sqrt(tot);x>=1;x--) { int y=tot/x; if(x*y!=tot) continue; int xx=x,yy=y; if (xx<yy) swap(xx,yy); if(ok(xx,yy,n)) { int c=(y+x)*2; if(c<minc) { //cout<<x<<" "<<y<<" "<<"qwq"<<endl; minc=c; ansx=max(x,tot/x); break; } } } printf("%d\n",minc); print(ansx,tot/ansx,n); } int main() { int t=gi(); while(t--) { int n=gi(); solve(n); } return 0; }
K
是一个位数
发现最多到,因为大于的时候剩余系被取完了。
所以暴力就好了。
记得特判
#include <bits/stdc++.h> #define endl '\n' using namespace std; typedef long long ll; const int INF = 0x3f3f3f3f; int n, MOD; ll mod(ll a, ll b) { return ((a + b) % MOD + MOD) % MOD; } ll mylog10(ll a) { int res = 0; while (a) res++, a /= 10; return res; } void solve() { cin >> n; if (n==1){ printf("0\n"); return; } MOD = n; ll ans = 0; for (int i = 1; i <= n; ++i) { ll ten = 10; for (int k = 1; k <= 7; ++k, ten *= 10) { if (mylog10(mod(i, -(i - 1) * ten)) <= k) { ans += k; break; } } } cout << ans << endl; } int main() { ios::sync_with_stdio(false); cin.tie(0); solve(); return 0; }
N
这签到题还挺好玩的(
考虑最后什么情况下不变,也就是每两个数之间都有子集关系
那就把每一位的全部堆到前缀去就好了。
爆了,写
#include <bits/stdc++.h> using namespace std; inline __int128 read() { __int128 x=0,f=1; char ch=getchar(); while(ch<'0'||ch>'9') { if(ch=='-') f=-1; ch=getchar(); } while(ch>='0'&&ch<='9') { x=x*10+ch-'0'; ch=getchar(); } return x*f; } inline void print(__int128 x){ if(x<0){ putchar('-'); x=-x; } if(x>9) print(x/10); putchar(x%10+'0'); } __int128 Sum; __int128 N,a[100005]; __int128 b[100005],c[100005]; int mx=0; int main(){ N=read(); for (int i=1;i<=N;i++){ a[i]=read(); int cnt = 0; Sum+=a[i]; while (a[i]){ if (a[i] & 1) b[cnt] ++; a[i] >>=1; cnt++; mx=max(mx,cnt); } } for (int i=0;i<=mx;i++){ for (int j=1;j<=b[i];j++){ c[j] |= (1ll<<i); } } __int128 f1=0; for (int i=1;i<=N;i++){ f1 = f1+1ll*(N*c[i]-Sum)*(N*c[i]-Sum); } //cout<<f1<<endl; __int128 f2=1ll*N*1ll*N*1ll*N; __int128 G=__gcd(f1,f2); f1/=G,f2/=G; print(f1); cout<<"/"; print(f2); return 0; }
赛后补题
A
现场脑子抽了才会想不出来这个题怎么弄
这种又排序又选序列的,可以先考虑一下定序然后再取子序列(其实这类题很多x)
考虑对一个序列施加小干扰
考虑两个相邻的数和
设他们前面的乘积为
在前的贡献是
在前的贡献是
得
在前的条件是
根据这东西排个序,然后
的时候为了当前贡献的计算不被前面选择的内容影响,需要倒着
每次选择一个数就相当于给后面的答案都乘上
//dp[i][j] = dp[i+1][j-1] * p[i] + w[i] #include<bits/stdc++.h> using namespace std; double dp[100005][21]; struct Node{ int w,q; }a[100005]; bool cmp(Node a,Node b){ return (a.w-b.w+a.q/10000.0*b.w-b.q/10000.0*a.w > 0); } int main(){ int N,M; scanf("%d%d",&N,&M); for (int i=1;i<=N;i++) scanf("%d",&a[i].w); for (int i=1;i<=N;i++) scanf("%d",&a[i].q); //for (int i=1;i<=N;i++) // cout<<a[i].q<<" "<<a[i].w<<endl; sort(a+1,a+N+1,cmp); for (int i=N;i>=1;i--) for (int j=0;j<=M;j++){ dp[i][j] = dp[i+1][j]; if (j-1>=0) dp[i][j] = max(dp[i][j],dp[i+1][j-1]*a[i].q/10000.+a[i].w); } printf("%.10lf",dp[1][M]); return 0; }
L
一万年见不到一次的题型(
首先,维基百科告诉我们:正凸多边形只有种
https://zh.m.wikipedia.org/zh-hans/%E6%AD%A3%E5%A4%9A%E9%9D%A2%E9%AB%94
然后我们就可以利用我们的立体几何知识对这五种立体图形发生一次坍缩产生的变化进行计算了。
然后就结束了(
#include <bits/stdc++.h> using namespace std; int T; int main(){ scanf("%d",&T); while (T--){ int N,a,K; scanf("%d%d%d",&N,&a,&K); double ansa = a; bool flag = false; while (K--){ if (N == 4){ N=4,ansa=ansa/3; }else if (N==6){ N=8,ansa=ansa*sqrt(2)/2; }else if (N==8){ N=6,ansa=ansa*sqrt(2)/3; }else if (N==12){ N=20,ansa=ansa*(3*sqrt(5)+5)/10; }else if (N==20){ N=12,ansa=ansa*(sqrt(5)+1)/6; } else{ printf("impossible\n"); flag = true; break; } } if (!flag){ printf("possible %d %.15lf\n",N,ansa); } } return 0; }
8.1牛客
不做评价,也没有题解。我觉得浪费了五小时(
8.2杭电
1007
首先发现一个点一个入一个出,所以一定是一堆环
每个环上的问题相当于从环上取个不相邻的数的方案数
组合数学分析一下,答案是
然后把每个环的生成函数一写,分治求一下的系数就好了。
#include<cstdio> #include<cstring> #include<algorithm> #include<cctype> #include<iostream> #define ll long long using namespace std; const ll T=30,P=998244353; const long long fish=998244353; int N,M; long long fac[500005],inv[500005]; long long Pow(long long x,long long y){ long long ans=1; for (;y;y>>=1,x=1ll*x*1ll*x%fish) if (y&1) ans=1ll*ans*1ll*x%fish; return ans; } void Pre(){ fac[0]=1; for (int i=1;i<=500000;i++) fac[i]=1ll*fac[i-1]*1ll*i%fish; inv[500000]=Pow(fac[500000],fish-2); for (int i=499999;i>=0;i--){ inv[i]=1ll*inv[i+1]*1ll*(i+1)%fish; } } long long C(long long n,long long r){ if (n<0 || r<0 || n-r<0) return 0; return 1ll*fac[n]*1ll*inv[r]%fish*1ll*inv[n-r]%fish; } struct Poly{ ll a[2000010],n; }F[T]; ll n,m,r[2000010],x[2000010],y[2000010]; bool v[T]; ll power(ll x,ll b){ ll ans=1; while(b){ if(b&1)ans=ans*x%P; x=x*x%P;b>>=1; } return ans; } void NTT(ll *f,ll n,ll op){ for(ll i=0;i<n;i++) if(i<r[i])swap(f[i],f[r[i]]); for(ll p=2;p<=n;p<<=1){ ll tmp=power(3,(P-1)/p),len=p>>1; if(op==-1)tmp=power(tmp,P-2); for(ll k=0;k<n;k+=p){ ll buf=1; for(ll i=k;i<k+len;i++){ ll tt=buf*f[i+len]%P; f[i+len]=(f[i]-tt+P)%P; f[i]=(f[i]+tt)%P; buf=buf*tmp%P; } } } if(op==-1){ ll invn=power(n,P-2); for(ll i=0;i<n;i++) f[i]=f[i]*invn%P; } return; } void Mul(Poly &F,Poly &G){ ll n=1; while(n<F.n+G.n)n<<=1; for(ll i=0;i<F.n;i++)x[i]=F.a[i]; for(ll i=0;i<G.n;i++)y[i]=G.a[i]; for(ll i=F.n;i<n;i++)x[i]=0; for(ll i=G.n;i<n;i++)y[i]=0; for(ll i=0;i<n;i++)r[i]=(r[i>>1]>>1)|((i&1)?(n>>1):0); NTT(x,n,1);NTT(y,n,1); for(ll i=0;i<n;i++)x[i]=x[i]*y[i]%P; NTT(x,n,-1); for(ll i=0;i<n;i++)F.a[i]=x[i]; F.n=F.n+G.n-1;return; } ll Find(){ for(ll i=0;i<T;i++) if(!v[i]){v[i]=1;return i;} } int Siz[500010],Size; bool vis[500010]; ll Solve(ll l,ll r){ if(l==r){ ll p=Find(); F[p].a[0]=1; long long NN = Siz[l]; for(ll i=1;i<=(Siz[l]+3)/2;i++){ F[p].a[i]=(C(NN-i-1,i-1)+C(NN-i,i))%P; F[p].n=(Siz[l]+3)/2+1; } return p; } ll mid=(l+r)>>1; ll ls=Solve(l,mid),rs=Solve(mid+1,r); Mul(F[ls],F[rs]);v[rs]=0; return ls; } int PP[500010]; signed main() { int T; Pre(); int K; scanf("%d",&T); while (T--){ scanf("%d%d",&N,&K); Size=0; for (int i=1;i<=N;i++){ scanf("%d",&PP[i]); vis[i]=0; Siz[i] = 0; } for (int i=1;i<=N;i++) if (!vis[i]){ Size++; int cntt=0; int x=i; while (!vis[x]){ cntt++; vis[x]=true; x=PP[x]; } Siz[Size] = cntt; } ll p=Solve(1,Size); printf("%lld\n",F[p].a[K]); } return 0; } /* 2 3 4 1 6 7 5 9 10 11 8 */
1010
签到,先手显然可以第一次就喊最大的那个数,然后数量
唯一会输的情况是俩顺子
#include <bits/stdc++.h> using namespace std; int T; int cnt1[10],cnt2[10]; int x,y; int main(){ scanf("%d",&T); int N; while (T--){ memset(cnt1,0,sizeof(cnt1)); memset(cnt2,0,sizeof(cnt2)); scanf("%d",&N); bool flag = false; for (int i=1;i<=N;i++){ scanf("%d",&x); cnt1[x]++; if (cnt1[x]!=1) flag = true; } for (int i=1;i<=N;i++){ scanf("%d",&y); cnt2[y]++; if (cnt2[y] !=1) flag = true; } //if (N<6 && cnt1[1] != 0 || cnt2[1] !=0 && !flag) flag = true; if (flag) printf("Win!\n"); else printf("Just a game of chance.\n"); } return 0; }
1012
考虑模拟这个过程
最开始写了俩堆然后T飞了(
考虑把每个时刻的加入分为两个操作
第一个操作是丢已经走的人
这个开个大根堆,每次取堆顶和当前时刻比较
第二个操作是加入人
这时候需要查询区间内人数最少的数的位置
写个线段树即可。
#include <bits/stdc++.h> using namespace std; struct Node1{ long long Time; int id; bool operator <(const Node1 &v)const{return Time>v.Time;} }; struct Node2{ long long a, s; }a[200100]; struct Node{ int cnt,id; }Tree[800100]; Node Min(Node a,Node b){ if (a.cnt == b.cnt && a.id < b.id) return a; if (a.cnt == b.cnt && a.id > b.id) return b; if (a.cnt<b.cnt) return a; if (a.cnt>b.cnt) return b; } void Add(int Now,int l,int r,int Ned,int val){ if (l==r){ Tree[Now].cnt += val; return; } int mid=(l+r)>>1; if (Ned <= mid) Add(Now<<1,l,mid,Ned,val); else Add(Now<<1|1,mid+1,r,Ned,val); Tree[Now] = Min(Tree[Now<<1],Tree[Now<<1|1]); } void Build(int Now,int l,int r){ if (l==r){ Tree[Now].cnt=0; Tree[Now].id=l; return; } int mid=(l+r)>>1; Build(Now<<1,l,mid);Build(Now<<1|1,mid+1,r); Tree[Now] = Min(Tree[Now<<1],Tree[Now<<1|1]); } bool temp(Node2 a,Node2 b){ return a.a<b.a; } long long ls[200100]; priority_queue<Node1> In; int T; int main(){ scanf("%d",&T); while (T--){ int N,M; scanf("%d%d",&N,&M); for (int i=1;i<=N;i++){ scanf("%lld%lld",&a[i].a,&a[i].s); } sort(a+1,a+N+1,temp); for (int i=1;i<=M;i++) ls[i] = 0; Build(1,1,M); for (int pos=1;pos<=N;pos++){ while (!In.empty()){ Node1 Now = In.top(); if (Now.Time > a[pos].a) break; In.pop(); Add(1,1,M,Now.id,-1); } Node Que = Tree[1]; ls[Que.id] = max(ls[Que.id],a[pos].a) + a[pos].s; Add(1,1,M,Que.id,1); In.push({ls[Que.id],Que.id}); } long long ans=0; for (int i=1;i<=M;i++) ans=max(ans,ls[i]); printf("%lld\n",ans); } return 0; }
补题
1011
一眼就感觉在哪见过(
的
但是当时不想手玩了,群论做法又每太弄明白,于是开摆了(
做法的话就是知道结论,这样操作的话,每个字符串的最小表示法一定只有种,人工跑出来比较就好了。
群论不会,会了再补(
#include <bits/stdc++.h> using namespace std; string ss1,ss2; map<string,string> mp; int main(){ mp["aa"] = ""; mp["bc"] = "a"; mp["ca"] = "bb"; mp["abc"] = ""; mp["aca"] = "abb"; mp["baa"] = "b"; mp["bab"] = "acc"; mp["bba"] = "c"; mp["bbb"] = ""; mp["bbc"] = "ba"; mp["cbc"] = "bb"; mp["cca"] = "cbb"; mp["ccc"] = "ab"; mp["abaa"] = "ab"; mp["abab"] = "cc"; mp["abba"] = "ac"; mp["abbb"] = "a"; mp["abbc"] = "aba"; mp["acbc"] = "abb"; mp["acca"] = "acbb"; mp["accc"] = "b"; mp["baca"] = "accb"; mp["bacc"] = "cb"; mp["cbaa"] = "cb"; mp["cbab"] = "bac"; mp["cbba"] = "cc"; mp["cbbb"] = "c"; mp["cbbc"] = "cba"; mp["ccba"] = "abac"; mp["ccbb"] = "aba"; mp["ccbc"] = "cbb"; mp["abaca"] = "ccb"; mp["abacb"] = "cbac"; mp["abacc"] = "acb"; mp["acbaa"] = "acb"; mp["acbab"] = "abac"; mp["acbac"] = "bacb"; mp["acbba"] = "acc"; mp["acbbb"] = "ac"; mp["acbbc"] = "acba"; mp["accba"] = "bac"; mp["accbb"] = "ba"; mp["accbc"] = "acbb"; mp["bacba"] = "cbac"; mp["bacbb"] = "cba"; mp["bacbc"] = "accb"; mp["cbaca"] = "bacb"; mp["cbacb"] = "acba"; mp["cbacc"] = "ccb"; int T; scanf("%d",&T); while (T--){ string s1,s2; cin>>s1; cin>>s2; int Len1=s1.length(); int Len2=s2.length(); string s3="",s4=""; for (int i=0;i<Len1;i++){ s3=s3+s1[i]; if (mp.count(s3)) s3=mp[s3]; } for (int i=0;i<Len2;i++){ s4=s4+s2[i]; if (mp.count(s4)) s4=mp[s4]; } if (s3 == s4) printf("yes\n"); else printf("no\n"); } }
1004
首先要读懂题意
范围很小
暴力状压一下每个控制点能看到的点,用或者
控制点能看到等价于两点之间连的直线不经过任何一条矩形的线段
因为范围很小,暴力跑即可。
至于判断方法,就直接判断线段的两端点是否在直线两侧即可,叉积一下。
#include <bits/stdc++.h> using namespace std; int N,M; struct Node{ long long x,y; void input(){ scanf("%lld%lld",&x,&y); } Node(int X=0,int Y=0){ x=X,y=Y; } Node operator - (Node A) const{ return Node(x-A.x,y-A.y); } long long operator * (Node A) const{ return (x*A.y-A.x*y); } bool operator == (Node A) const{ return (x == A.x && y == A.y); } }q[155][5],p[155]; int sgn(long long x){ if (x==0) return 0; return (x>0?1:-1); } bool Cross(Node A,Node B,Node C,Node D){ return (sgn((B-A)*(C-A))*sgn((B-A)*(D-A))) <= 0;//判断线段与直线相交 } bool Check(Node A,Node B){ for (int i=0 ; i<M ; i++){ for (int j = 0 ;j <= 3 ; j++){ if ((B == q[i][j]) || (B == q[i][(j+1)%4])) continue;//判断点重复 if (Cross(A,B,q[i][j],q[i][(j+1)%4]) && Cross(q[i][j],q[i][(j+1)%4],A,B)) return 0; } } return 1; } void output(__int128 x) { if (!x) return ; if (x < 0) putchar('-'),x = -x; output(x / 10); putchar(x % 10 + '0'); } __int128 S[155][5],u=1; long long T[55]; void Solve(){ memset(S,0,sizeof(S)); memset(T,0,sizeof(T)); scanf("%d%d",&N,&M); for (int i=0 ; i < N;i++){ p[i].input(); } for (int i = 0 ; i < M; i++) for (int j = 0 ; j <= 3 ; j++) q[i][j].input(); for (int i = 0 ; i < N ; i++ ) for (int j=0 ; j < N ; j ++){ if (i==j) continue; if (Check(p[i],p[j])){ T[i] |= 1<<j; } } for (int i = 0 ; i< N ;i++) for (int j = 0 ; j < M ; j++) for (int k = 0 ;k < 4 ; k++) if (Check(p[i],q[j][k])){ S[i][k] |= (u<<j); } int ans = 1e9; long long MS = ((1<<N)-1); __int128 SS = ((u<<M)-1); for (int ST=0;ST<=MS;ST++){ __int128 a1=0,a2=0,a3=0,a4=0; int t=0,cnt = 0; for (int i = 0 ; i < N ; i++){ if (ST>>i&1){ a1 |= S[i][0]; a2 |= S[i][1]; a3 |= S[i][2]; a4 |= S[i][3]; t |= T[i]; cnt ++; } } if (a1 == SS && a2 == SS && a3 == SS && a4 == SS && (t&ST) == ST){ ans = min(ans,cnt); } } if (ans == 1e9){ cout<<"No Solution!\n"; } else cout<<ans<<endl; } int main(){ int T; scanf("%d",&T); while (T--){ Solve(); } }
1002(未完成)
写个想法(
考虑把期望那个式子拆一拆,因为一个数的因子要和原本的数字相同
考虑一个数质因数分解后的结果,会发现其实这个东西是,其中是当前数质因子分解后每个指数的大小
所以这个数的贡献就是:
其实可以单独拿出去乘
所以把后面的东西的和设为
考虑它在和处的函数值
而且显然这函数是积性的
用亚线性筛法筛出前缀和应该就可以了。
题解好像用的筛,回头用筛试试能不能筛出来(
8.4杭电
七夕节?单刷节!
1006
树形,考虑一下每个点的最大怎么算就好了。
#include <bits/stdc++.h> using namespace std; int T,N; vector<int> G[500005]; long long dp[500005],Siz[500005]; void dfs(int Now,int fa){ Siz[Now] = 1; for (auto v:G[Now]){ if (v == fa) continue; dfs(v,Now); dp[Now] = max(dp[Now],dp[v]); Siz[Now] += Siz[v]; } dp[Now] += Siz[Now]; } int main(){ scanf("%d",&T); while (T--){ scanf("%d",&N); for (int i=1;i<=N;i++){ G[i].clear(); dp[i]=0; Siz[i]=0; } for (int i=1;i<N;i++){ int u,v; scanf("%d%d",&u,&v); G[u].push_back(v); G[v].push_back(u); } dfs(1,1); printf("%lld\n",dp[1]); } return 0; }
1007
计算的方案
两边把的逆元一乘
然后记录一下每个余数的最小值
每次询问扫一遍个,每次判断一下步数和最小的关系
#include <bits/stdc++.h> using namespace std; long long s[5005],d[5005]; unordered_map<int,int> Ti; long long Pow(long long x,long long y,long long P){ long long ans=1; for (;y;){ if (y&1) ans=ans*x%P; x=1ll*x*1ll*x%P; y>>=1; } return ans; } int main(){ int T; scanf("%d",&T); while (T--){ long long P,a,N,q; Ti.clear(); scanf("%lld%lld%lld%lld",&P,&a,&N,&q); for (int i=1;i<=N;i++){ scanf("%lld%lld",&s[i],&d[i]); s[i] = Pow(s[i],P-2,P); } long long nw=1; for (int i=0;i<=200000;i++){ if (!Ti.count(nw)) Ti[nw] = i; nw = nw*a%P; } while (q--){ int x; scanf("%d",&x); int ans=0; for (int i=1;i<=N;i++){ long long nww = 1ll*x*s[i]%P; if (x==0 && s[i] == 0 || Ti.count(nww) && Ti[nww] <= d[i]) ans++; //if () ans++; } printf("%d\n",ans); } } }
1010
yy一下发现破成一个森林就好了
要求字典序最小,跑一边
#include <bits/stdc++.h> using namespace std; int fa[100005]; int Getfa(int x){ return ((x==fa[x])?x:fa[x]=Getfa(fa[x])); } int u[400005],v[400005]; int cnt=0; int id[400005]; int main(){ int T; scanf("%d",&T); while (T--){ int N,M; cnt=0; scanf("%d%d",&N,&M); for (int i=1;i<=N;i++) fa[i]=i; for (int i=1;i<=M;i++){ scanf("%d%d",&u[i],&v[i]); } for (int i=M;i>=1;i--){ int fx=Getfa(u[i]),fy=Getfa(v[i]); if (fx == fy) id[++cnt]=i; else fa[fy] = fx; } printf("%d\n",cnt); for (int i=cnt;i>=1;i--) printf("%d ",id[i]); printf("\n"); } return 0; }
1009
赛场上想的是坐标变换解矩阵,然后赛后发现解一个向量系数就好了(
代码赛场上来不及写了,回头补吧(
8.8牛客
C
队友写的,好像是个二分图匹配quq
#include <bits/stdc++.h> #define endl '\n' using namespace std; typedef long long ll; const int N = 5e5 + 5; const int INF = 0x3f3f3f3f; int n, f[N], ans[N]; vector<pair<int,int>> v; void solve() { cin >> n; v.clear(); for (int i = 1; i <= n; ++i) f[i] = ans[i] = 0; for (int i = 1; i <= n; ++i) { int a; cin >> a; if (!f[a]) { f[a] = true; v.push_back(make_pair(a, i)); } } if (v.size() == 1) { cout << "NO" << endl; return; } cout << "YES" << endl; for (int i = 0; i < v.size() - 1; ++i) ans[v[i].second] = v[i + 1].first; ans[v[v.size() - 1].second] = v[0].first; int p = 1; while (p <= n && f[p]) p++; for (int i = 1; i <= n; ++i) { if (ans[i]) { cout << ans[i] << " "; continue; } cout << p++ << " "; while (p <= n && f[p]) p++; } cout << endl; } int main() { ios::sync_with_stdio(false); cin.tie(0); int T; cin >> T; while (T--) solve(); return 0; }
F
发现两种操作之间互相自己不影响
所以其实怎么删都无所谓
能删就删就好了。
拿链表模拟这个过程
记得特判一下
#include <bits/stdc++.h> #define endl '\n' using namespace std; typedef long long ll; const int N = 1e5 + 5; const int INF = 0x3f3f3f3f; int n, nex[N], pre[N], t[N], f[N], m; void solve() { cin >> n >> m; if (n <= 1) { cout << 0 << endl; return; } for (int i = 1; i <= n; ++i) { cin >> t[i]; if (i < n) nex[i] = i + 1; if (i > 1) pre[i] = i - 1; } nex[n] = 1; pre[1] = n; int ans = 0; for (int i = 1; !f[i]; i = nex[i]) { f[i] = 1; bool is = true; while (is) { is = false; if (t[pre[i]] == t[i] || t[pre[i]] + t[i] == m) { ans++; is = true; nex[pre[pre[i]]] = nex[i]; pre[nex[i]] = pre[pre[i]]; i = nex[i]; f[i] = 1; } if (n - ans * 2 <= 1) break; if (t[nex[i]] == t[i] || t[nex[i]] + t[i] == m) { ans++; is = true; nex[pre[i]] = nex[nex[i]]; pre[nex[nex[i]]] = pre[i]; i = pre[i]; f[i] = 1; } if (n - ans * 2 <= 1) break; } } cout << ans << endl; } int main() { ios::sync_with_stdio(false); cin.tie(0); solve(); return 0; }
G
我佛了啊,什么阅读理解题(
首先搞清楚正则表达式怎么写
然后就会发现,其实和可以表示所有字符串
那么答案长度就是
但是长度为时
以为例
还会有
这四种
所以样例是
但是所有元素都相同的话
比如
会有
和
所以答案多种
根据这个 一下就好了。
#include <bits/stdc++.h> using namespace std; int T; string ss; int main(){ scanf("%d",&T); while (T--){ cin>>ss; if (ss.length() == 1){ printf("1 2\n"); continue; } int Len = ss.length(); bool flag=false; for (int i=1;i<Len;i++) if (ss[i] != ss[i-1]) flag = true; if (Len == 2){ if (!flag) printf("2 8\n"); else printf("2 6\n"); } else{ if (!flag) printf("2 4\n"); else printf("2 2\n"); } } return 0; }
J
发现考虑原序列很难
其实我们发现,一个前缀和序列一定能还原一个原序列
然后就考虑前缀和序列(模意义下)
前缀和序列,有个相等的时候会贡献个和为的组
而这些位置在哪其实并不影响答案
问题就转化成,有种前缀和,塞入个位置,搞出组满足条件的方案数
背包一下就好了。
表示用了个位置,组满足条件的方案
然后转移的时候枚举一下当前放的前缀和是多少,满足条件的组数,有几个位置,以及当前的前缀和放几个位置就好了。
想到前缀和之后一下子就通了(
#include <bits/stdc++.h> using namespace std; const int N=70,P=998244353; int n,k,t,C[N][N],f[N][N*N]; int Pre[N]; int main(){ scanf("%d%d%d",&n,&k,&t); C[0][0]=1; for (int i=1;i<=n;i++) Pre[i+1] = Pre[i]+i; for (int i=1;i<=n;++i){ C[i][0]=C[i][i]=1; for (int j=1; j<i;++j) C[i][j] = (C[i-1][j-1]+C[i-1][j])%P; } for (int i=0;i<=n&&Pre[i+1]<=t;++i) f[i][Pre[i+1]]=C[n][i]; for (int i=1;i<k;++i) for (int j=n;j;j--) for (int l=1;l<=j &&Pre[l]<=t;l++) for (int r=0;r<=t-Pre[l];r++) if (f[j-l][r]) f[j][r+Pre[l]]=(1ll*f[j-l][r]*C[n-j+l][l]+f[j][r+Pre[l]])%P; cout<<f[n][t]; return 0; }
补题
K
怎么会有人不会莫队啊
下面感性的证几个结论(
首先,只有堆的情况先手是必胜的
这个情况显然
那么,有堆的情况呢?
首先,显然的,两个人都不会主动去使用操作
因为这会使对方获得一个先手只有一堆的情况,而刚才说过了,这种情况先手必胜。
那么就只有操作
这就是个经典的游戏
继续推广,到的情况
我们发现,先手一定能通过从最大的那堆里面取一些石头,放到最小的那堆里,使得剩下的两堆相等
而这根据上面的分析,这种情况下先手是必败的(经典的游戏的结论)
那么,之后就是类似的操作了,可以归纳证明
也就是说,区间长度为奇数的情况下,先手是必胜的
区间长度为偶数的情况下,是个游戏
于是我们就考虑,计算区间长度为偶数的,且游戏必败的区间
也就是对每个询问,问区间内有多少个长度为偶数且和为的子区间。
发现可以离线询问
然后我就不会了
然后显然每次增删一个数字的时间是
跑个莫队就好了。
#include<bits/stdc++.h> using namespace std; int blo; #define bel(x) (x-1)/blo + 1 struct Node{ int l,r,id; }a[100005]; long long Ans[100005],ans; int Sum[100005]; long long cnt[2][2000005]; bool temp(Node a,Node b){ if (bel(a.l) != bel(b.l)) return (a.l<b.l); else return (bel(a.l)&1)?(a.r<b.r):(a.r>b.r); } void Add(int x){ ans -= cnt[x&1][Sum[x]]*(cnt[x&1][Sum[x]]-1)/2; cnt[x&1][Sum[x]] ++; ans += cnt[x&1][Sum[x]]*(cnt[x&1][Sum[x]]-1)/2; } void Del(int x){ ans -= cnt[x&1][Sum[x]]*(cnt[x&1][Sum[x]]-1)/2; cnt[x&1][Sum[x]] --; ans += cnt[x&1][Sum[x]]*(cnt[x&1][Sum[x]]-1)/2; } int main(){ int N,Q; scanf("%d%d",&N,&Q); for (int i=1;i<=N;i++){ scanf("%d",&Sum[i]); Sum[i]--; Sum[i]^=Sum[i-1]; } for (int i=1;i<=Q;i++){ scanf("%d%d",&a[i].l,&a[i].r); a[i].l--; a[i].id=i; } blo = sqrt(N); sort(a+1,a+Q+1,temp); int l=1,r=0; for (int i=1;i<=Q;i++){ while (l > a[i].l) Add(--l); while (r < a[i].r) Add(++r); while (l < a[i].l) Del(l++); while (r > a[i].r) Del(r--); long long Len = r-l; Ans[a[i].id] = 1ll*Len*1ll*(Len+1)/2 - ans; } for (int i=1;i<=Q;i++) printf("%d\n",Ans[i]); return 0; }
E
有趣的数据结构(
首先先离散化。
考虑一个数字,一定只有两种可能:一种放在峰值左边,一种放在峰值右边。
而我们贪心一下,优先移动步数少的
然后可以发现的是,根据冒泡排序,一个数的移动次数和它的逆序对数量有关。
而我们就可以拉两个树状数组,分别维护在峰值左边和在峰值右边的数字是什么
然后用树状数组求逆序对
然后我们可以发现一件事情:一个数移动到峰值左边的时候,它的步数是不会变的,而它移到峰值右边的时候,步数一定是不降的。
然后就会发现,这个事情是这样的:
对于一个移动到右边的数来说,加入一个比它小的数,会导致它的逆序对数量,也就是移动到端的代价
然后会发现,因为每个数的的步数都不一样,直接维护值和值不太好维护
发现我们只需要知道它们的大小关系,也就是说只要维护就可以了(感觉在很多里也很经常使用这个手段)
线段树维护区间的最小值
然后考虑每次加一个新的值的影响:
1、修改和树状数组里的值
2、修改的值,意味着的值(因为不变)
3、利用线段树找到所有在这轮中的点,暴力把他们从树状数组中删除
统计答案即可。
至于峰是最高还是最低,只要队数字做一下反转再跑一遍就好了。
#include <bits/stdc++.h> using namespace std; int N; vector<int> mp; const int mx = 2e5+5; struct Seg{ int Tree[mx<<2],Tag[mx<<2]; void Build(int Now,int l,int r){ if (l == r){ Tree[Now] = 1e9; Tag[Now] = 0; return; } int mid = (l+r)>>1; Build(Now<<1,l,mid); Build(Now<<1|1,mid+1,r); Tag[Now] = 0; Tree[Now] = 1e9; } void PushDown(int Now){ if (Tag[Now]){ Tag[Now<<1] += Tag[Now]; Tag[Now<<1|1] += Tag[Now]; Tree[Now<<1] += Tag[Now]; Tree[Now<<1|1] += Tag[Now]; Tree[Now] = min(Tree[Now<<1],Tree[Now<<1|1]); Tag[Now] = 0; } } void Get(int Now,int l,int r){ if (Tree[Now]) return; if (l==r){ mp.push_back(l); return; } PushDown(Now); int mid = (l+r)>>1; Get(Now<<1,l,mid),Get(Now<<1|1,mid+1,r); } void Modify(int Now,int l,int r,int Pos,int Nd){ if (l==r){ Tree[Now] = Nd; return; } PushDown(Now); int mid=(l+r)>>1; if (Pos<=mid) Modify(Now<<1,l,mid,Pos,Nd); else Modify(Now<<1|1,mid+1,r,Pos,Nd); Tree[Now] = min(Tree[Now<<1],Tree[Now<<1|1]); } void Add(int Now,int l,int r,int L,int R,int Nd){ if (L<=l&&r<=R){ Tree[Now] += Nd; Tag[Now] += Nd; return; } PushDown(Now); int mid = (l+r)>>1; if (L <= mid) Add(Now<<1,l,mid,L,R,Nd); if (mid <R) Add(Now<<1|1,mid+1,r,L,R,Nd); Tree[Now] = min(Tree[Now<<1],Tree[Now<<1|1]); } }seg; struct BIT{ int Tree[mx]; void Clear(){memset(Tree,0,sizeof(Tree));} int LowBit(int x){return (x&(-x));} void Modify(int Pos,int nd){for (int i=Pos;i<=mx;i+=LowBit(i)) Tree[i] += nd;} int Get(int Pos){int ans = 0; for (int i = Pos; i ;i-=LowBit(i)) ans += Tree[i]; return ans;} }TL,TR; int a[mx],b[mx]; long long Ans[mx]; void init(){ scanf("%d",&N); for (int i=1;i<=N;i++){ scanf("%d",&a[i]); b[i] = a[i]; } sort(b+1,b+N+1); for (int i=1;i<=N;i++){ a[i] = lower_bound(b+1,b+N+1,a[i])-b; } } void Solve(){ long long ans = 0; for (int i=1;i<=N;i++){ //cout<<TR.Get(1)<<" "<<TR.Get(3)<<endl; ans = ans + TR.Get(N)-TR.Get(a[i]); TL.Modify(a[i],1),TR.Modify(a[i],1); seg.Add(1,1,N,a[i],N,-1); seg.Modify(1,1,N,a[i],TL.Get(a[i]-1)); mp.clear(); seg.Get(1,1,N); for (auto nw:mp){ //cout<<i<<" "<<nw<<endl; TR.Modify(nw,-1); seg.Modify(1,1,N,nw,1e9); } Ans[i] = min (Ans[i] , ans); } } void Work(){ memset(Ans,63,sizeof(Ans)); TL.Clear(); TR.Clear(); seg.Build(1,1,N); Solve(); for (int i=1;i<=N;i++){ a[i] = N-a[i]+1; } TL.Clear(); TR.Clear(); seg.Build(1,1,N); Solve(); for (int i=1;i<=N;i++) cout<<Ans[i]<<'\n'; } int main(){ init(); Work(); return 0; } /* 3 3 1 2 */
8.9杭电
1003
关于我完全不知道题解在写什么东西这件事
下面是自己赛场上的做法
首先需要注意的,题目的树是无根树,当成图跑就行了,条件是没有环。
考虑一个火柴人,显然它的头部是比较关键的
我们就先枚举一下头部下面的那个中心点(对于样例来说是枚举点)
接下来,显然点只要不和选出来的构成身子和手的点重复就行了
方案数是
然后再考虑怎么不重不漏的算三个点
我们对于每个这种点,遍历它的所有出边
记录前缀的四个数据
1、前缀中一只手的数量
2、一只脚的数量
3、两只手的数量
4、一只手一只脚的数量
那么算答案的时候,只要考虑当前遍历到的这个点的出度提供的是手还是脚,然后更新这四个数值就好了。
因为是前缀,所以不会有选择同一个点同时作为两个部件的情况,而且不重不漏
具体四个数值之间的转移可以看看代码(
#include <bits/stdc++.h> using namespace std; const int fish = 998244353; int ans = 0; vector<int> E[500005]; int d[500005]; void calc(int x){ int H=0,HL=0,L=0,HH=0; for (auto v:E[x]){ int u=x; int Hd=d[v]-1; int Lg=1ll*(d[v]-1)*(d[v]-2)/2%fish; ans = (ans + 1ll*Lg*HH%fish*(d[u]-3)%fish)%fish; ans = (ans + 1ll*Hd*HL%fish*(d[u]-3)%fish)%fish; HH = (HH + 1ll*Hd*H%fish)%fish; HL = (HL + 1ll*Lg*H%fish)%fish; HL = (HL + 1ll*Hd*L%fish)%fish; (H+=Hd)%=fish;(L+=Lg)%=fish; } } int main(){ int T; scanf("%d",&T); while (T--){ ans = 0; int N; scanf("%d",&N); for (int i=1;i<=N;i++){ E[i].clear(); d[i] = 0; } for (int i=1;i<N;i++){ int u,v; scanf("%d%d",&u,&v); E[u].push_back(v); E[v].push_back(u); d[u]++,d[v]++; } for (int i=1;i<=N;i++) calc(i); printf("%d\n",ans); } }
04
队友写的。
大概就是能配就配,大力分类讨论
注意黑色的自己能和自己配
#include <bits/stdc++.h> #define endl '\n' using namespace std; typedef long long ll; const int INF = 0x3f3f3f3f; int e, l, r, b; int Min() { int res = e; if (!l && !r) res += min(b, 1); res += max(l, r); return res; } int Max() { int res = 0; res += l + r; res += e + min(e + 1, b); return res; } void solve() { cin >> e >> l >> r >> b; cout << Min() << " " << Max() << endl; } int main() { ios::sync_with_stdio(false); cin.tie(0); int T; cin >> T; while (T--) solve(); return 0; }
1006
其实是个数位板子……
表示的是位,个,是否分出大小,前导零。
但是这题有一点比较特殊
如果按照常规的数位写的话,需要枚举这一位填了什么
但是太大,直接枚举会
考虑一件事,其实转移的时候
只有填入和是比较特殊的两个转移
所以转移的时候把这两个单独拿出来,其他的跑一遍然后统一乘系数就行了。
是个细节特别多的数位
#include <bits/stdc++.h> using namespace std; #define ll long long ll Mi; const ll fish = 1e9+7; ll dp[75][75]; bool vis[75][75]; int d; ll B,l,r; ll a[105]; int cnt; ll Pow(ll x,ll y){ ll ans=1; if (x==0 && y==0) return 0; for (;y;y>>=1){ if (y&1) ans = ans * x%fish; x=x*x%fish; } return ans; } void Get(ll x,ll K){ cnt = 0; while (x){ a[cnt++] = x%K; x/=K; } } ll dfs(int k,bool flag,bool Z,int DT){ if (k<0) return Pow(DT,Mi); if (!flag && !Z && vis[k][DT]) return dp[k][DT]; if (!flag && !Z) vis[k][DT] = true; ll ans = 0; if (flag){ if (a[k] == d){ (ans+=dfs(k-1,flag,0,DT+1))%=fish; (ans+=dfs(k-1,0,0,DT)*1ll*(d-1)%fish)%=fish; (ans+=dfs(k-1,0,Z,DT))%=fish; } if (a[k] < d){ (ans += dfs(k-1,0,0,DT)*1ll*(a[k]-1)%fish)%=fish; (ans += dfs(k-1,1,0,DT))%=fish; (ans += dfs(k-1,0,Z,DT))%=fish; } if (a[k] > d){ (ans+=dfs(k-1,1,0,DT))%=fish; if (d == 0){ if(!Z) (ans+=dfs(k-1,0,Z,DT+1))%=fish; else (ans+=dfs(k-1,0,Z,DT))%=fish; (ans+=dfs(k-1,0,0,DT)*1ll*(a[k]-1)%fish)%=fish; } else{ (ans+=dfs(k-1,0,0,DT+1))%=fish; (ans+=dfs(k-1,0,Z,DT))%=fish; (ans+=dfs(k-1,0,0,DT)*1ll*(a[k]-2)%fish)%=fish; } } } if (!flag){ if (d==0){ (ans+=dfs(k-1,flag,0,DT)*1ll*(B-1)%fish)%=fish; if (Z) (ans += dfs(k-1,flag,Z,DT))%=fish; if (!Z) (ans += dfs(k-1,flag,Z,DT+1))%=fish; } else{ (ans +=dfs(k-1,flag,Z,DT))%=fish; (ans +=dfs(k-1,flag,0,DT+1))%=fish; (ans +=dfs(k-1,flag,0,DT)*1ll*(B-2)%fish)%=fish; } } if (!flag && !Z) dp[k][DT]=ans; return ans; } int main(){ int T; scanf("%d",&T); while (T--){ scanf("%lld%lld%d%lld%lld",&Mi,&B,&d,&l,&r); Get(r,B); memset(vis,0,sizeof(vis)); memset(dp,0,sizeof(dp)); ll ans = dfs(cnt-1,1,1,0); if (l>1){ memset(vis,0,sizeof(vis)); memset(dp,0,sizeof(dp)); Get(l-1,B); ans -= dfs(cnt-1,1,1,0); (ans+=fish)%=fish; } printf("%lld\n",ans); } return 0; }
1008
猜结论
感性猜想每一步任取都有办法让三个数能构成三角形
把三个数视为石头堆,因为要维持堆数不变
所以每一个石头堆先
然后就是游戏
有点像昨天的一个题
#include <bits/stdc++.h> using namespace std; int a,b,c; int main(){ int T; scanf("%d",&T); while (T--){ scanf("%d%d%d",&a,&b,&c); a--,b--,c--; int x=a^b^c; if (x==0) printf("Lose\n"); else printf("Win\n"); } return 0; }
8.11杭电
坐牢(
1001
队友写的,看起来像个分类讨论
#include <bits/stdc++.h> #define endl '\n' using namespace std; typedef long long ll; const int INF = 0x3f3f3f3f; string s; void solve() { cin >> s; int cnt00, cnt01, cnt10, cnt11; cnt00 = cnt01 = cnt10 = cnt11 = 0; for (int i = 0; i < s.size(); ++i) s[i] -= '0'; for (int i = 0; i < s.size(); i += 2) { if (s[i] == 0 && s[i + 1] == 1) cnt01++; else if (s[i] == 0 && s[i + 1] == 0) cnt00++; else if (s[i] == 1 && s[i + 1] == 0) cnt10++; else cnt11++; } if (s.size() & 1) { if (s.back() == 0) swap(cnt01, cnt10); int k = min(cnt01, cnt10); cnt11 += k; cnt00 += k; cnt01 -= k; cnt10 -= k; if (s.back() == 0) cout << 0; for (int i = 1; i <= cnt00; ++i) cout << "00"; for (int i = 1; i <= cnt01; ++i) cout << "01"; for (int i = 1; i <= cnt10; ++i) cout << "10"; for (int i = 1; i <= cnt11; ++i) cout << "11"; if (s.back() == 1) cout << 1; cout << endl; return; } int k = min(cnt01, cnt10); cnt11 += k; cnt00 += k; cnt01 -= k; cnt10 -= k; for (int i = 1; i <= cnt00; ++i) cout << "00"; for (int i = 1; i <= cnt01; ++i) cout << "01"; for (int i = 1; i <= cnt10; ++i) cout << "10"; for (int i = 1; i <= cnt11; ++i) cout << "11"; cout << endl; } int main() { ios::sync_with_stdio(false); cin.tie(0); int T; cin >> T; while (T--) solve(); return 0; }
1004
签到,结论是
#include <bits/stdc++.h> using namespace std; int main() { int t; cin >> t; while(t--) { int n; cin >> n; cout <<2*n <<endl; } return 0; }
1008
树形,有一点像最大匹配,但是不完全一样
表示到点,点选/不选,选了是否已经匹配的最大取点
然后直接转移即可。
#include <bits/stdc++.h> #define endl '\n' using namespace std; typedef long long ll; const int N = 500000 + 5; const int INF = 0x3f3f3f3f; int n, dp[N][3]; // 0 - not selected 1 - selected 2 - selected and compared int hed[N], nex[N * 2], e[N * 2], ct; void dfs(int x, int fa) { dp[x][0] = dp[x][2] = 0; dp[x][1] = 1; int sum = 0; for (int i = hed[x]; i; i = nex[i]) { if (e[i] == fa) continue; dfs(e[i], x); sum += dp[e[i]][0]; } for (int i = hed[x]; i; i = nex[i]) { if (e[i] == fa) continue; dp[x][0] += max({dp[e[i]][0], dp[e[i]][1], dp[e[i]][2]}); dp[x][1] += dp[e[i]][0]; dp[x][2] = max(dp[x][2], sum - dp[e[i]][0] + dp[e[i]][1] + 1); } } void solve() { cin >> n; for (int i = 1; i <= n; ++i) hed[i] = 0; ct = 0; for (int i = 1; i < n; ++i) { int a, b; cin >> a >> b; e[++ct] = b, nex[ct] = hed[a], hed[a] = ct; e[++ct] = a, nex[ct] = hed[b], hed[b] = ct; } dfs(1, 1); // for (int i = 1; i <= n; ++i) // cout << dp[i][0] << " " << dp[i][1] << " " << dp[i][2] << endl; cout << max({dp[1][0], dp[1][1], dp[1][2]}) << endl; } int main() { ios::sync_with_stdio(false); cin.tie(0); int size(512<<20); // 512M __asm__ ( "movq %0, %%rsp\n"::"r"((char*)malloc(size)+size)); int t; cin >> t; while (t--) solve(); exit(0); return 0; }
1011
题目很长,但其实冷静分析一下就会发现问题是把长和宽分别划分,划分完之后另一边的最小长度是知道的
枚举一边然后直接算即可。
#include <bits/stdc++.h> using namespace std; #define ll long long int main(){ int T; scanf("%d",&T); while (T--){ ll N,M,K,ans=0; scanf("%lld%lld%lld",&N,&M,&K); /*for (int i=1;i<=sqrt(K);i++){ if (K%i == 0){ ll a=i,b=K/i; ll k1=(N-a)/a,k2=(M-b)/b; if (k1>=0&&k2>=0) ans=max((k1+1)*(k2+1),ans); k1=(M-a)/a.k2=(N-b)/b; if (k1>=0 && k2>=0) ans=max((k1+1)*(k2+1),ans); } }*/ for (int i=1;i<=sqrt(N);i++){ if (N%i==0){ ll a=i,b=(K+a-1)/a; if (b!=0){ ll k1=N/a,k2=M/b; if (k1>0&&k2>0) ans = max(ans,k1-1+k2-1); } a=N/i,b=(K+a-1)/a; if (b!=0){ ll k1=N/a,k2=M/b; if (k1>0&&k2>0) ans=max(ans,k1+k2-2); } } } for (int i=1;i<=sqrt(M);i++){ if (M%i==0){ ll a=i,b=(K+a-1)/a; if (b!=0){ ll k1=M/a,k2=N/b; //cout<<a<<" "<<b<<" "<<k1<<" "<<k2<<endl; if (k1>0&&k2>0)ans = max(ans,k1+k2-2); } a=M/i,b=(K+a-1)/a; if (b!=0){ ll k1=M/a,k2=N/b; if (k1>0&&k2>0) ans = max(ans,k1+k2-2); } } } printf("%lld\n",ans); } return 0; }
赛后补题
1007
惯性思维了。
开始看到这种完全图,满脑子算法+优化选边,然后发现优化组间选边因为边是乘法根本做不出来(
实际上,这道题要发现的性质是,因为,在和分别取和的时候,每条边一定是比小的(因为是个排列)
其实,我们就可以发现,最小生成树的每条边都比小
,那么一定有一个数比来的小
于是我们枚举那个数,可以在的时间内算出每一条边,然后跑一遍就好了
值得注意的是,我们需要开个桶来排序,如果直接用的话,因为边数最坏是级别的,所以多一个会(本机大概跑12s)
#include<bits/stdc++.h> using namespace std; int fa[50005]; int p[50005],pos[50005]; struct Node{ int u,v; }; vector<Node> Edge[50005]; int Getfa(int x){return ((x==fa[x])?x:fa[x]=Getfa(fa[x]));} int main(){ int T; scanf("%d",&T); while (T--){ int N; scanf("%d",&N); for (int i=1;i<=N;i++) Edge[i].clear(); for (int i=1;i<=N;i++){ scanf("%d",&p[i]); pos[p[i]]=i; } int mx = sqrt(N); for (int i=1;i<=N;i++) for (int j=i+1; j<=i+mx && j<=N; j++){ int tmp; tmp = abs(i-j) * abs(p[i] - p[j]); if (tmp < N) Edge[tmp].push_back({i,j}); tmp = abs(pos[i] - pos[j]) * abs(i-j); if (tmp < N) Edge[tmp].push_back({pos[i],pos[j]}); } //sort(Edge.begin(),Edge.end(),temp); int cnt=0; long long ans = 0; for (int i=1;i<=N;i++) fa[i] = i; for (int i=1;i<=N;i++) for (auto nw : Edge[i]){ int fx=Getfa(nw.u),fy=Getfa(nw.v); if (fx == fy) continue; fa[fx]=fy; cnt++; ans = ans+i; if (cnt == N-1) break; } printf("%d\n",ans); } return 0; }
1002
作为团队的数据结构手我太弱了(
dp[i]表示到i点的方案,转移显然是把所有的前面的合法点扔进来场上题读完就知道,拉个单调栈,然后分奇偶维护和,显然单调栈弹出的时候会影响一个区间,让合法变成不合法(因为最大/最小值的位置改变了)
现在问题来了,怎么维护这个过程?
我场上想的一个个暴力然后暴力跑结果飞辣(
然而显然这个过程可以拉一个线段树来维护。
可以转移的位置显然最大最小值要同时满足条件
那么我们考虑一个的过程,最大值位置的改变,是不是意味着本来最大值位置在的合法会因为这个而改变。
我们先把这部分改变扔掉,也就是这段区间中,位置和共奇偶的位置,满足的条件数量被了
最小值同理就好了。
单调栈,完再同理的把这个元素到栈的末尾
但是还有一个需要注意的点
就是区间是的时候,可以转移的点是
所以值的要偏移一个位置。
#include <bits/stdc++.h> const int fish = 998244353; const int mxx = 3e5+5; using namespace std; struct Seg{ int Tag[4*mxx],mx[4*mxx]; long long Sum[4*mxx]; void PushDown(int Now){ int x=Tag[Now]; mx[Now<<1] += x; mx[Now<<1|1] += x; Tag[Now<<1]+=x; Tag[Now<<1|1]+=x; Tag[Now] = 0; } void Build(int Now,int l,int r){ Tag[Now] = mx[Now] = Sum[Now] = 0; if (l==r) return; int mid = (l+r)>>1; Build(Now<<1,l,mid),Build(Now<<1|1,mid+1,r); } void Update(int Now,int l,int r,int L,int R,int val){ //cout<<Now<<" "<<L<<" "<<R<<" "<<endl; if(L<=l&&r<=R){ Tag[Now] += val; mx[Now] += val; return; } int mid = (l+r)>>1; if (Tag[Now]) PushDown(Now); if (L<=mid) Update(Now<<1,l,mid,L,R,val); if (mid<R) Update(Now<<1|1,mid+1,r,L,R,val); mx[Now] = max(mx[Now<<1],mx[Now<<1|1]); Sum[Now] = 0; if (mx[Now<<1] == mx[Now]) (Sum[Now] += Sum[Now<<1]+fish)%=fish; if (mx[Now<<1|1] == mx[Now]) (Sum[Now] += Sum[Now<<1|1]+fish)%=fish; } void Insert(int Now,int l,int r,int Pos,int val){ if (l==r){ Sum[Now] = val; return; } int mid =(l+r)>>1; if (Tag[Now]) PushDown(Now); if (Pos <= mid) Insert(Now<<1,l,mid,Pos,val); else Insert(Now<<1|1,mid+1,r,Pos,val); mx[Now] = max(mx[Now<<1],mx[Now<<1|1]); Sum[Now] = 0; if (mx[Now<<1] == mx[Now]) (Sum[Now] += Sum[Now<<1]+fish)%=fish; if (mx[Now<<1|1] == mx[Now]) (Sum[Now] += Sum[Now<<1|1]+fish)%=fish; } }Tree[2]; int tp1,tp2; int a[mxx],dp[mxx],stk1[mxx],stk2[mxx]; void Solve(){ int N; scanf("%d",&N); for (int i=1;i<=N;i++) scanf("%d",&a[i]); Tree[0].Build(1,1,N); Tree[1].Build(1,1,N); tp1=tp2=0; dp[0]=1; for (int i=1;i<=N;i++){ Tree[i&1].Insert(1,1,N,i,dp[i-1]); while (tp1 && a[i]>a[stk1[tp1]]){ Tree[(stk1[tp1]&1)].Update(1,1,N,stk1[tp1-1]+1,stk1[tp1],-1); tp1--; } stk1[++tp1] = i; Tree[i&1].Update(1,1,N,stk1[tp1-1]+1,i,1); while (tp2 && a[i]<a[stk2[tp2]]){ Tree[(stk2[tp2]&1)^1].Update(1,1,N,stk2[tp2-1]+1,stk2[tp2],-1); tp2--; } stk2[++tp2] = i; Tree[(i&1)^1].Update(1,1,N,stk2[tp2-1]+1,i,1); dp[i] = 0; //cout<<Tree[0].mx[1]<<" "<<Tree[1].mx[1]<<endl; if (Tree[0].mx[1] == 2) (dp[i] += Tree[0].Sum[1])%=fish; if (Tree[1].mx[1] == 2) (dp[i] += Tree[1].Sum[1])%=fish; } printf("%d\n",dp[N]); return; } int main(){ //freopen("1002.in","r",stdin); int T; scanf("%d",&T); while (T--){ Solve(); } return 0; }
8.15牛客
A
签到,双指针随便扫扫。
#include<bits/stdc++.h> using namespace std; int N,M,cnt=0; int a[100010]; int b[100010]; long long ans = 0; int main(){ scanf("%d%d",&N,&M); for (int i=1;i<=N;i++) scanf("%d",&a[i]); int r=1; for (int l=1;l<=N;l++){ while (r<=N && cnt<M){ if (b[a[r]] == 0) cnt++; b[a[r]]++; r++; } //cout<<l<<" "<<r<<" "<<cnt<<endl; if (cnt == M) ans = ans + (N-r+2); if (b[a[l]] == 1) cnt--; b[a[l]] --; } cout<<ans; return 0; }
B
首先两个人完全等价,只考虑一个人的情况
表示到,走了步
方程是(能到)
直接转移会飞
考虑优化
优化的话,把每个跳跃看成一个线段,差分+前缀和即可。
#include <bits/stdc++.h> using namespace std; int a[9005],inv[9005]; vector<int> aa[8005]; const int fish = 998244353; int Pow(int x,int y){ int ans = 1; for(;y;y>>=1){ if (y&1) ans = 1ll*ans*x%fish; x=1ll*x*x%fish; } return ans; } int dp[8005][8005]; int ans = 0; int main(){ int N; scanf("%d",&N); for (int i=1;i<N;i++){ scanf("%d",&a[i]); inv[i] = Pow(a[i],fish-2); } //dp[1][1]=1; dp[1][0]=1; for (int i=1;i<=N;i++){ for (int j=1;j<=N;j++) aa[j].clear(); int x= dp[i][i-1]*1ll*inv[i]%fish; aa[i+a[i]].push_back(x); for (int j=i+1;j<N;j++){ dp[j][i] = x; int tmp = 1ll*dp[j][i-1]*1ll*inv[j]%fish; //cout<<j<<" "<<i-1<<" "<<dp[j][i-1]<<endl; aa[j+a[j]].push_back(tmp);x=(x+tmp)%fish; for (auto nw : aa[j]){ x = (x-nw+fish)%fish; } } ans = (ans + 1ll*x*x%fish)%fish; } cout<<ans; return 0; }
G
模板
话说这么普及了吗
把建出来
暴力跳遍历一下就好。
#include<bits/stdc++.h> using namespace std; typedef long long ll; const int N=300010,M=1e6+5,inf=0x3f3f3f3f,mod=1e9+7; #define mst(a) memset(a,0,sizeof a) #define lx x<<1 #define rx x<<1|1 #define reg register #define PII pair<int,int> #define fi first #define se second string a = "",ss; int len[N],fail[N],cnt=1,cur,num[N],ans,tri[N][55],u,Times[N]; int get_fail(int x,int i){ while(i-len[x]-1<0||a[i-len[x]-1]!=a[i]) x=fail[x]; return x; } int main(){ int N; memset(Times,0,sizeof(Times)); cin>>N; for (int i=1;i<=N;i++){ cin>>ss; a = a + ss+ char('z' + 1 + i); } len[1]=-1,fail[0]=1; int n=a.length(); int nw = 0; for(int i=0;i<n;i++){ if (a[i]>'z'){ nw++; } u=get_fail(cur,i); if(!tri[u][a[i]-'a']){ len[++cnt]=len[u]+2; fail[cnt]=tri[get_fail(fail[u],i)][a[i]-'a']; tri[u][a[i]-'a']=cnt; } cur=tri[u][a[i]-'a']; Times[cur] |=(1<<nw); } int ST=(1<<N)-1; long long ans = 0; for (int i=cnt;i;i--){ for (int x=i;x;x=fail[x]){ Times[fail[x]] |= Times[x]; if ((Times[fail[x]] & Times[x])== Times[x]) break; } if (Times[i] == ST) ans++; } cout<<ans; return 0; }
I
容易想到一个
表示划分到
直接转移飞
发现区间最大值相同的段可以合并转移
写一发单调栈就可以了。
注意每次弹栈的时候合并一下最大值区间就好了。
有点像前两天的一个题
#include <bits/stdc++.h> using namespace std; int a[10005]; int dp[8005][8005]; int stk[8005],tp,ans[8006],dpmn[8005]; int N; int main(){ memset(dp,63,sizeof(dp)); cin>>N; dp[0][0]=0; for (int i=1;i<=N;i++) scanf("%d",&a[i]); for (int i=1;i<=N;i++){ tp=0; for (int j=i;j<=N;j++){ int mn = dp[i-1][j-1]; while (tp && stk[tp]<=a[j]){ mn = min(mn,dpmn[tp]); tp--; } stk[++tp] = a[j]; dpmn[tp] = mn; ans[tp] = mn+a[j]; if (tp>1) ans[tp] = min(ans[tp],ans[tp-1]); dp[i][j] = ans[tp]; } } for (int i=1;i<=N;i++){ cout<<dp[i][N]<<endl; } return 0; }
8.16 牛客
有点事,没怎么打。
1008
冷静思考一下会发现答案最大只有,因为最差可以和
然后问题就变成统计同时和跟互质的数的个数
容斥一下即可。
#pragma GCC optimize(2) #include<bits/stdc++.h> using namespace std; set<int> nw; bool isPrime[10000010]; int Prime[10000010], mp[10000010],cnt = 0; inline long long read()//inline 加速读入 { long long x=0;char c=getchar();//x代表返回值,c代表读取的字符 while (c<'0'||c>'9') c=getchar();//读取所有非数部分 while (c>='0'&&c<='9') x=x*10+c-'0',c=getchar();//如果读取的字符为数,加入返回值 return x; } void Pre(int mx){ for (int i = 2; i <= mx; i++){ if (!isPrime[i]){ Prime[++cnt] = i; mp[i]=i; } for (int j = 1; j <= cnt && i * Prime[j] <= mx; j++){ isPrime[i * Prime[j]] = 1; mp[i*Prime[j]] = Prime[j]; if (i % Prime[j] == 0) break; } } } void GetP(int x){ while (x!=1){ int y = mp[x]; while (x%y==0) x/=y; if (nw.find(y) == nw.end()) nw.insert(y); } } vector<int> nww; /*long long calc(long long x){ long long ans=0; int sz=nww.size(); for (int ST=1;ST<1<<sz;ST++){ int cnt=0,m=1; for (int i=0;i<sz;i++) if (ST>>i&1) ++cnt,m*=nww[i]; if (cnt & 1) ans+=x/m; else ans-=x/m; } return x-ans; }*/ int N,Q; int m,ret; void dfs(int x,int s,int o){ if (x==m){ ret+=o*(N/s); return; } dfs(x+1,s,o); if (s<=N/nww[x]) dfs(x+1,s*nww[x],-o); } int main(){ //freopen("input.txt","r",stdin); //freopen("test.out","w",stdout); N=read(),Q=read(); Pre(N); while (Q--){ int u,v; u=read();v=read(); if (__gcd(u,v) == 1){ printf("1 1\n"); continue; } nw.clear();nww.clear(); GetP(u);GetP(v); for (auto x:nw) nww.push_back(x); m=nww.size(); ret = 0; dfs(0,1,1); printf("%d %lld\n",2,ret+(__gcd(u,v) == 2)); } }
过了然而状压 麻了,不是很理解为什么
发现答案只有一种情况,是的累乘
#include <bits/stdc++.h> using namespace std; const int fish = 998244353; void Solve(){ int N; long long ans = 1; scanf("%d",&N); for (int i=1;i<=N;i++){ long long x; scanf("%lld",&x); ans = ans*(x+1)%fish; } printf("%lld\n",(ans-1+fish)%fish); } int main(){ int T; cin>>T; while (T--) Solve(); }
1007
是不能与这个套娃放在一组的套娃数量
这个拿个指针维护就行。
#include <bits/stdc++.h> using namespace std; int T; const int fish = 998244353; long long dp[5005][5005]; int a[5005]; void Solve(){ memset(dp,0,sizeof(dp)); int N,K,r; scanf("%d%d%d",&N,&K,&r); for (int i=1;i<=N;i++) scanf("%d",&a[i]); dp[0][0]=1; long long pos = 0; for (int i=1;i<=N;i++){ while (a[pos+1] <= a[i]-r && pos+1<i) pos++; for (int j=i-pos;j<=i;j++){ dp[i][j] = (dp[i-1][j-1]+dp[i-1][j]*1ll*(j-(i-pos-1))%fish)%fish; } } cout<<dp[N][K]<<endl; } int main(){ scanf("%d",&T); while (T--){ Solve(); } }
8.17 牛客
E
出题人能不能把题意写清楚点……
先考虑最后个空位,因为这个人任意一个人动了的话,那就会导致自己成为倒数第个
所以最后个人一定不会复读
以此类推,每次向前推个位置,就会发现,只有前个人会选择复读,后面的人都相互牵制。
然后就是这个题目题意写的不清楚的地方了(
其实每个人只能看到前面的人的选择,而不能看到后面
所以它只会能取则取。
#include <bits/stdc++.h> using namespace std; int N,P; int flag[5005]; int ans[5005]; int a[5005][5005]; int main(){ scanf("%d%d",&N,&P); for (int i=1;i<=N;i++) for (int j=1;j<=N;j++) scanf("%d",&a[i][j]); for (int i=1;i<=N;i++){ int mx=0,pos=0; if (i<=N%P) cout<<a[i][i]<<" "; else cout<<0<<" "; } return 0; }
H
发现后缀的个数只和质因子中跟的个数有关,是
然后问题就好做了。
考虑一个点的答案构成,显然是由子树的所有点和外面的点
而外面的点其实一定在到的链上,可以在的过程中顺便统计一下。
子树内的点直接算
然后就结束了
#include <bits/stdc++.h> #define endl '\n' using namespace std; typedef long long ll; const int N = 1e5 + 5; const int INF = 0x3f3f3f3f; int n, q; ll cnt2[N], cnt5[N], ans[N], siz[N]; int e[N * 2], nex[N * 2], hed[N], ct; void dfs1(int x, int fa) { siz[x] = 1; int k = x; while (!(k % 2)) { cnt2[x]++; k /= 2; } while (!(k % 5)) { cnt5[x]++; k /= 5; } for (int i = hed[x]; i; i = nex[i]) { if (e[i] == fa) continue; dfs1(e[i], x); siz[x] += siz[e[i]]; } } void dfs2(int x, int fa, ll sum2, ll sum5) { ll Sum2 = sum2 + siz[x] * cnt2[x]; ll Sum5 = sum5 + siz[x] * cnt5[x]; ans[x] = min(Sum2, Sum5); for (int i = hed[x]; i; i = nex[i]) { if (e[i] == fa) continue; dfs2(e[i], x, sum2 + (siz[x] - siz[e[i]]) * cnt2[x], sum5 + (siz[x] - siz[e[i]]) * cnt5[x]); } } void solve() { cin >> n >> q; for (int i = 1; i < n; ++i) { int a, b; cin >> a >> b; e[++ct] = b, nex[ct] = hed[a], hed[a] = ct; e[++ct] = a, nex[ct] = hed[b], hed[b] = ct; } dfs1(1, 1); dfs2(1, 1, 0, 0); while (q--) { int a; cin >> a; cout << ans[a] << endl; } } int main() { ios::sync_with_stdio(false); cin.tie(0); solve(); return 0; }
J
考虑先破环成链
然后发现,因为这个赋值操作只能往前赋值。
所以其实每次只需要考虑两种操作下,这个点向前延伸最远能到哪就好。
用表示从点开始,向前延伸到的最远位置
记得多考虑一下可能出现增完之后,导致和前面的段相同进行的合并。
#include<cstdio> int T,n,a[2000050],f[2000050]; int main(){ scanf("%d",&T); while (T--){ int flag=0; scanf("%d",&n); for (int i=1;i<=n;i++) scanf("%d",&a[i]); for (int i=1;i<=n;i++) a[i+n]=a[i]; f[1]=1; int m=n*2; for (int i=2;i<=m;i++){ if (a[i]==a[i-1]||a[i]==(a[i-1]+1)%3) f[i]=f[i-1]; else f[i]=i; if (f[i]!=1&&a[i]==a[f[i]-1])f[i]=f[f[i-1]-1]; if (i-f[i]+1>=n){ flag=1; break; } } if (flag)printf("Yes\n"); else printf("No\n"); } }
M
签到,模拟
出题人能不能把题意写清楚点
#include<cstdio> double p[5][6]={{0,0,0,0,0,0},{0,1,1,0.8,0.5,0},{0,2,2,1.6,1,0},{0,3,3,2.4,1.5,0},{0,5,5,2.5,2,0}}; int c[15][15]; double ans; int main(){ for (int i=1;i<=4;i++) for (int j=1;j<=5;j++) scanf("%d",&c[i][j]); double sum1=0,sum2=0; for (int i=1;i<=4;i++){ for (int j=1;j<=5;j++){ sum1+=p[i][1]*c[i][j]; sum2+=p[i][j]*c[i][j]; } } ans+=sum2/sum1*100; sum1=sum2=0; for (int i=1;i<=5;i++) sum1+=c[4][i]; sum2=c[4][1]*1.0+c[4][2]*0.5+c[4][3]*0.4+c[4][4]*0.3; ans+=sum2/sum1; printf("%.12lf\n",ans); }
8.18杭电
我就是个寄吧
今天单刷
1001
竞赛排名问题,考虑网络流
首先,因为这个人想让获胜
所以先考虑让能赢则赢
然后考虑分配剩下的局数
发现,剩下的人在剩下的局里一定最多只能赢轮
考虑分配剩下的局数
把比赛当成点
然后向比赛点连一条容量为的边
比赛点向比赛双方连一条容量为的边
然后每个人向连容量的边
这样跑最大流之后,跑出来的东西就是能胜场数量
而胜场数量一定是和剩下的场次数量相等的(因为比赛一定会有输赢)
直接跑就好了。
#include<bits/stdc++.h> using namespace std; const int maxn=1e6+50; const long long INF=2e18; int n,m,s,t,ecnt; int dis[5005],head[5005]; long long ans=0; struct mint{ int nxt,v; long long w; } e[maxn<<1]; inline void addline(int u,int v,long long w) { e[ecnt].nxt=head[u]; e[ecnt].v=v; e[ecnt].w=w; head[u]=ecnt++; e[ecnt].nxt=head[v]; e[ecnt].v=u; e[ecnt].w=0; head[v]=ecnt++; } int BFS() { queue<int> q; memset(dis,0,sizeof(dis)); dis[s]=1; q.push(s); while(!q.empty()) { int now=q.front(); q.pop(); for(int i=head[now]; ~i; i=e[i].nxt) { int v=e[i].v; if(e[i].w==0 || dis[v]) continue; dis[v]=dis[now]+1; q.push(v); } } return dis[t]; } long long DFS(int now,long long lim) { if(now==t || !lim) return lim; long long res=0; for(int i=head[now]; ~i; i=e[i].nxt) { int v=e[i].v; if(e[i].w==0 || dis[v]!=dis[now]+1) continue; long long add=DFS(v,min(lim,e[i].w)); e[i].w-=add; e[i^1].w+=add; res+=add; lim-=add; if(!lim) break; } if(!res) dis[now]=-1; return res; } int Dinic() { int ans=0; while (BFS()) { ans+=DFS(s,1e6); } return ans; } int win[5005]; int u[5005],v[5005]; int p[5005]; int main() { int TT; scanf("%d",&TT); while (TT--){ int N,M1,M2; scanf("%d%d%d",&N,&M1,&M2); ecnt = 0; int Point = N; s=1; for (int i=1;i<=5000;i++) head[i] = -1; memset(win,0,sizeof(win)); for (int i=1;i<=M1;i++){ int x,y,z; scanf("%d%d%d",&x,&y,&z); if (z == 1) win[x]++; else win[y]++; } for (int i=1;i<=M2;i++){ scanf("%d%d",&u[i],&v[i]); if (u[i] == 1 || v[i] == 1) win[1]++; } int cntt = 0; for (int i=1;i<=M2;i++){ if (u[i] == 1 || v[i] == 1) continue; Point ++; addline(s,Point,1); addline(Point,u[i],1); addline(Point,v[i],1); cntt++; } t=++Point; bool flag = false; for (int i=2;i<=N;i++){ if (win[1] < win[i]){ flag = true; break; } addline(i,t,win[1]-win[i]); } if (flag){ printf("NO\n"); continue; } // cout<<"qwq"<<endl; if (Dinic() == cntt){ printf("YES\n"); } else printf("NO\n"); } return 0; }
1003
题目很唬人但其实只有两种可能
一种是大小交替,一种是小大交替
模拟即可。
记得等号是要取的
#include <bits/stdc++.h> using namespace std; int T,N; long long a[1000006],b[1000006]; int main(){ scanf("%d",&T); while (T--){ scanf("%d",&N); for (int i=1;i<=N;i++){ scanf("%lld",&a[i]); b[i] = a[i]; } long long ans1=0,ans2=0; for (int i=2;i<=N;i++){ if (i&1){ if (a[i] <= a[i-1]){ ans1 += abs(a[i]-(a[i-1]+1)); a[i] = a[i-1]+1; } if (b[i]>=b[i-1]){ ans2 += abs(b[i]-(b[i-1]-1)); b[i] = b[i-1]-1; } } else{ if (a[i]>=a[i-1]){ ans1 += abs(a[i]-(a[i-1]-1)); a[i] = a[i-1]-1; } if (b[i]<=b[i-1]){ ans2 += abs(b[i]-(b[i-1]+1)); b[i] = b[i-1]+1; } } } long long ans = min(ans1,ans2); printf("%lld\n",ans); } return 0; }
1007
奇数块分不出合法解
问题转化成所有两边都是偶数的边的个数为
答案为
#include<bits/stdc++.h> using namespace std; const int N = 100005; vector<int> f[N]; int du[N]; int ans[N]; int n; int u, v; int aim; int number = 0; const int fish = 998244353; int Pow(int x,int y){ int anss = 1; for (;y;y>>=1){ if (y&1) anss = 1ll*anss*x%fish; x=1ll*x*x%fish; } return anss; } int getans(int x, int pre) { ans[x] = 1; int len = f[x].size(); if (len == 1 && x != aim) { return ans[x]; } for (int i = 0; i < len; i++) { int to = f[x][i]; if (to == pre) { continue; } getans(to, x); ans[x] += ans[to]; } if(ans[x]%2==0&&ans[x]!=n) number++; return ans[x]; } void Solve(){ number = 0; scanf("%d",&n); if (n%2) { cout << -1 << endl; return; } for (int i=1;i<=n;i++){ f[i].clear(),f[i].clear(); du[i]=0; ans[i] = 0; } for (int i = 1; i <= n - 1; i++) { scanf("%d",&u),scanf("%d",&v); f[u].push_back(v); f[v].push_back(u); du[u]++, du[v]++; } for (int i = 1; i <= n; i++) { if (du[i] == 1) { aim = i; getans(i, -1); break; } } printf("%d\n",Pow(2,number)-1); } int main() { int T; scanf("%d",&T); while (T--){ Solve(); } return 0; }
1009
读完题
猜测一下最优策略
会发现,一定是黑 白 黑 和 黑 白 白 黑两种情况交错
然后回发现,和的先手的情况下,涂色是以为一个周期的
会先涂左手第二个,而会先涂左手第三个
根据这个去把循坏节推出来就行了。
虽然现场是交了几发wa调出来的循环节(
#include <bits/stdc++.h> using namespace std; int N; string S; int Alice[]={0,1,1,1,2,2,3}; int Bob[] = {0,1,1,2,2,3,3}; int main(){ int T; cin>>T; while (T--){ cin>>N>>S; int ans = 3*(N/7); if (N%7 !=0){ if (S=="Alice")ans = ans + Alice[N%7]; else ans = ans + Bob[N%7]; } printf("%d\n",ans); } }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?