9.4 三场选拔赛补题
选拔赛题目来自一些杭电比赛的签到题
到都签不出来,这是怎么绘世呢
8.23信心up(down)场
因为不会开题和复健大失败被创死了
A:杭电多校第四场Link with Equilateral Triangle
题意:由边长为1的小三角形组成的边长为n的大三角形,往每个点填0或1或2,三大边分别不能填0/1/2,问有没有填法使每个小三角形三顶点的和不是3的倍数,读入n
当时不敢猜结论,打了个表才确信
一律输出no
不贴代码了
B:杭电多校第四场BIT Subway
题意:输出两种买票规则下分别的答案:正常买票,>=100后八折,>=200后五折 & 如果一张票买之前不到100(200),买之后超过了,就把它拆开算,拆成买到刚好100(200)和剩余的可以吃折扣的部分
当时居然因为没考虑到从<100一次买票直接买到>200的情况然后wa了
模拟一下,刚复健代码很丑
1 #include<iostream> 2 #include<cstdio> 3 using namespace std; 4 int t,n; 5 double x,sum1,sum2; 6 int main() 7 { 8 scanf("%d",&t); 9 while(t--){ 10 scanf("%d",&n); 11 sum1=sum2=0; 12 for(int i=1;i<=n;i++){ 13 scanf("%lf",&x); 14 double x1=x; 15 while(x1){ 16 if(sum1<100){ 17 if(sum1+x1>100){ 18 x1-=100-sum1; 19 sum1=100; 20 } 21 else sum1+=x1,x1=0; 22 } 23 else if(sum1<200){ 24 if(sum1+x1*0.8>200)sum1=200+(x1-(1.25*(200-sum1)))*0.5,x1=0; 25 else sum1+=x1*0.8,x1=0; 26 } 27 else sum1+=x1*0.5,x1=0; 28 } 29 if(sum2<100)sum2+=x; 30 else if(sum2<200)sum2+=x*0.8; 31 else sum2+=x*0.5; 32 } 33 printf("%.3lf %.3lf\n",sum1,sum2); 34 } 35 return 0; 36 } 37 /* 38 2 39 13 40 30 20 23 20 7 20 11 12 30 20 30 15 13 41 3 42 10 200 10 43 44 */
C:杭电多校第三场Cyber Language
题意:给出多行字符,每行多个单词,要求把每行的每个单词首位以大写输出,其它字母忽略
也是模拟,整串读入以后遇到空格输出大写
1 #include<iostream> 2 #include<cstdio> 3 using namespace std; 4 int t,flag; 5 char s[110]; 6 int main() 7 { 8 scanf("%d",&t); 9 getchar(); 10 while(t--){ 11 gets(s); 12 flag=0; 13 for(int i=0;s[i]!=0;i++){ 14 if(!flag){ 15 flag=1; 16 printf("%c",s[i]-'a'+'A'); 17 } 18 if(s[i]==' ')flag=0; 19 } 20 printf("\n"); 21 } 22 return 0; 23 }
D:杭电多校第六场Maex
题意:一棵树,每个节点都可以填数,每个节点的数不能重复。一个点的分数为它子树中没有出现的最小的非负整数,求整棵树的最大分数和
树形DP,我直到现在才知道我写了个树形DP
能够被计算进答案的是从某个叶节点开始的子树串(不是节点串),同层的其它子树都只会取到0,而这一个从0开始填数的子树能算进答案的数量是子树siz
一开始尝试了深度和子树大小,后来列了反例以后发现直接用子树答案进行传递就可以了
1 #include<iostream> 2 #include<cstdio> 3 #include<queue> 4 using namespace std; 5 const int N=5e5+10; 6 int t,n,dep[N],tag[N],ans,siz[N],son[N]; 7 int ver[N<<1],head[N],tot,Next[N<<1]; 8 long long sum[N]; 9 void add(int x,int y){ 10 ver[++tot]=y; 11 Next[tot]=head[x]; 12 head[x]=tot; 13 } 14 void dfs(int x,int f){ 15 siz[x]=1; 16 son[x]=0; 17 for(int i=head[x];i;i=Next[i]){ 18 int y=ver[i]; 19 if(y==f)continue; 20 dfs(y,x); 21 siz[x]+=siz[y]; 22 if(sum[y]>sum[son[x]])son[x]=y; 23 } 24 sum[x]=sum[son[x]]+siz[x]; 25 } 26 int main() 27 { 28 scanf("%d",&t); 29 while(t--){ 30 scanf("%d",&n); 31 ans=tot=0; 32 for(int i=1;i<=n;i++)head[i]=0; 33 for(int i=1,x,y;i<n;i++){ 34 scanf("%d%d",&x,&y); 35 add(x,y),add(y,x); 36 } 37 dfs(1,0); 38 printf("%lld\n",sum[1]); 39 } 40 return 0; 41 }
E:杭电多校第九场Sum Plus Product
题意:n个数字,每次操作随机拿出两个数字a、b,并放回a*b+a+b这个数字,问剩下的最后一个数字的期望
手写样例以后发现和操作方案无关,答案是个固定值
列出了计算过程,维护答案就用每一个a[i]乘上之前的每组乘积,这就是新的答案组,并把它自己加入答案组
1 #include<iostream> 2 #include<cstdio> 3 using namespace std; 4 const int N=510; 5 const int mod=998244353; 6 int t,n; 7 int a[N]; 8 long long ans,sum,sum1; 9 int main() 10 { 11 scanf("%d",&t); 12 while(t--){ 13 sum1=sum=ans=0; 14 scanf("%d",&n); 15 for(int i=1;i<=n;i++){ 16 scanf("%d",&a[i]); 17 if(!sum)sum1=a[i],sum=a[i]; 18 else{ 19 sum1=(sum*a[i]%mod+a[i])%mod; 20 sum=(sum+sum1)%mod; 21 } 22 ans=(ans+sum1)%mod; 23 } 24 printf("%lld\n",ans); 25 26 } 27 28 return 0; 29 }
F:杭电多校第十场Even Tree Split
题意:给出一棵树,保证节点数为偶数,现在删去一些边使每个连通块大小为偶数,问有多少种删边方案
删多少边删哪条边的不同都可以成为新答案,只要子树节点数是偶数那这条边就可以删,因为每一条删掉的边都满足子树大小是偶数,被删去的部分也是偶数,所以删掉每条边以后受到影响的连通块的大小还是偶数
1 #include<iostream> 2 #include<cstdio> 3 using namespace std; 4 const int N=1e5+10; 5 const int mod=998244353; 6 int t,n,sum,siz[N]; 7 int head[N],tot,Next[N<<1],ver[N<<1]; 8 long long F[N],inv[N],ans; 9 long long pw(long long x,int p){ 10 long long num=1; 11 while(p){ 12 if(p&1)num=(num*x)%mod; 13 x=x*x%mod; 14 p>>=1; 15 } 16 return num; 17 } 18 void pre(int x){ 19 F[0]=0,F[1]=1; 20 for(int i=2;i<=x;i++)F[i]=(F[i-1]*i)%mod; 21 inv[x]=pw(F[x],mod-2); 22 for(int i=x-1;i>=0;i--)inv[i]=(inv[i+1]*(i+1))%mod; 23 } 24 void add(int x,int y){ 25 ver[++tot]=y; 26 Next[tot]=head[x]; 27 head[x]=tot; 28 } 29 void dfs(int x,int f){ 30 siz[x]=1; 31 for(int i=head[x];i;i=Next[i]){ 32 int y=ver[i]; 33 if(y==f)continue; 34 dfs(y,x); 35 siz[x]+=siz[y]; 36 if(!(siz[y]&1))sum++; 37 } 38 } 39 long long C(int x,int y){ 40 return F[x]*inv[y]%mod*inv[x-y]%mod; 41 } 42 int main() 43 { 44 scanf("%d",&t); 45 pre(N-10); 46 while(t--){ 47 scanf("%d",&n); 48 sum=ans=tot=0; 49 for(int i=1;i<=n;i++)head[i]=0; 50 for(int i=1,x,y;i<n;i++){ 51 scanf("%d%d",&x,&y); 52 add(x,y),add(y,x); 53 } 54 dfs(1,0); 55 for(int i=1;i<=sum;i++){ 56 ans=(ans+C(sum,i))%mod; 57 } 58 printf("%lld\n",ans); 59 } 60 return 0; 61 }
G:杭电多校第八场Orgrimmar
题意:给出一棵树,要求选一些点每个点最多向选出的点集内连一条边,求能选出的最大点数
树形dp,维护dp[x][0/1][0/1]表示当前点选不选,子树选不选
一开始没想清楚具体定义,如果第三维子树选不选为1,代表的含义是可以选,而不是必须选
1 #include<bits/stdc++.h> 2 using namespace std; 3 const int N=5e5+10; 4 int t,n; 5 int ver[N<<1],head[N],tot,Next[N<<1]; 6 int sum[N][2][2]; 7 void add(int x,int y){ 8 ver[++tot]=y; 9 Next[tot]=head[x]; 10 head[x]=tot; 11 } 12 void dfs(int x,int f){ 13 sum[x][0][0]=sum[x][0][1]=0; 14 sum[x][1][1]=sum[x][1][0]=0; 15 for(int i=head[x];i;i=Next[i]){ 16 int y=ver[i]; 17 if(y==f)continue; 18 dfs(y,x); 19 sum[x][0][0]+=max(sum[y][0][0],sum[y][0][1]); 20 sum[x][0][1]+=max(sum[y][1][0],max(sum[y][1][1],max(sum[y][0][1],sum[y][0][0]))); 21 } 22 sum[x][1][0]=sum[x][0][0]+1; 23 for(int i=head[x];i;i=Next[i]){ 24 int y=ver[i]; 25 if(y==f)continue; 26 sum[x][1][1]=max(sum[x][1][1],sum[y][1][0]+sum[x][0][0]-max(sum[y][0][0],sum[y][0][1])+1); 27 } 28 } 29 int main() 30 { 31 int size(512<<20); // 512M 32 __asm__ ( "movq %0, %%rsp\n"::"r"((char*)malloc(size)+size)); 33 scanf("%d",&t); 34 while(t--){ 35 scanf("%d",&n); 36 tot=0; 37 for(int i=1;i<=n;i++)head[i]=0; 38 for(int i=1,x,y;i<n;i++){ 39 scanf("%d%d",&x,&y); 40 add(x,y),add(y,x); 41 } 42 dfs(1,0); 43 printf("%d\n",max(sum[1][0][0],max(sum[1][0][1],max(sum[1][1][0],sum[1][1][1])))); 44 } 45 exit(0); 46 // return 0; 47 } 48 /* 49 1 50 10 51 1 2 52 2 4 53 3 2 54 5 3 55 6 4 56 7 5 57 8 3 58 9 8 59 10 7 60 */
H:杭电多校第二场ShuanQ
题意:给出两个密钥数字P、Q和加密数据,存在M,保证 Q = P ^ (-1),P×Q≡1modM。有加密公式: 加密数据=原始数据*P modM,解密公式:原始数据=加密数据*Q modM。问能否求出原始数据
P×Q≡1modM,那么M一定是PQ-1的质因数,且M大于P、Q和加密数据
因为对单个数字进行质因数分解相关的求解,数字也不大,所以用根号筛
判断一下所有满足条件的原始数据是不是同一个数字就知道符不符合题意
(实际上M>P、Q,这样的M最多只有一个)
1 #include<iostream> 2 #include<cstdio> 3 using namespace std; 4 const int N=2e6+10; 5 int t,cnt,flag; 6 long long p,q,b,c; 7 bool fid(long long x){ 8 for(long long i=2;i*i<=x;i++){ 9 if(x%i==0){ 10 if(!flag&&i>c&&i>q&&i>p){ 11 b=c*q%i; 12 flag=1; 13 } 14 else if(b!=c*q%i&&i>c&&i>q&&i>p)return 0; 15 while(x%i==0)x/=i; 16 } 17 } 18 if(x!=1){ 19 if(x>c&&x>q&&x>p){ 20 if(!flag){ 21 b=c*q%x; 22 flag=1; 23 } 24 else if(b!=c*q%x)return 0; 25 } 26 } 27 return flag; 28 } 29 int main() 30 { 31 scanf("%d",&t); 32 while(t--){ 33 scanf("%lld%lld%lld",&p,&q,&c); 34 flag=0; 35 if(fid(p*q-1))printf("%lld\n",b); 36 else printf("shuanQ\n"); 37 } 38 return 0; 39 }
I:杭电多校第七场Triangle Game
题意:给出三角形的三边,有两个玩家轮流操作,每次减少任意一边至少1的值,如果操作完三边不能构成三角形即输。问先手是否能获胜。
考场上猜了一个小时的结论,把所有好像接触过的可能的知识点都试了一遍,然后发现(a-1)^(b-1)^(c-1)==0则先手输
赛后看了证明,但没有太看懂,先放个tag在这里
1 #include<iostream> 2 #include<cstdio> 3 using namespace std; 4 int t,a,b,c,cnt; 5 int main() 6 { 7 scanf("%d",&t); 8 while(t--){ 9 scanf("%d%d%d",&a,&b,&c); 10 if(((a-1)^(b-1)^(c-1))==0)printf("Lose\n"); 11 else printf("Win\n"); 12 } 13 return 0; 14 }
J:杭电多校第六场Map
题意:给出两张大小地图分别的四角的坐标,左上角对应,问大小地图重合的同一点的坐标(即这个点在小地图上的位置等于它在大地图上的位置,在小地图上的坐标和在大地图上的坐标代表地图的同一个地方)
设一张地图左上角为O,左下角为A,右上角为B,地图上的目标点(答案点)为M,一定有μOA+αOB=OM(向量)
两个地图都有一个这样的式子,解方程就好了
1 #include<iostream> 2 #include<cstdio> 3 using namespace std; 4 int t; 5 double d,e,xx,yy; 6 double x[10],y[10]; 7 int main() 8 { 9 scanf("%d",&t); 10 while(t--){ 11 for(int i=0;i<8;i++){ 12 scanf("%lf%lf",&x[i],&y[i]); 13 } 14 double a1=x[2]-x[3]-x[6]+x[7],b1=x[0]-x[3]-x[4]+x[7],c1=x[7]-x[3]; 15 double a2=y[2]-y[3]-y[6]+y[7],b2=y[0]-y[3]-y[4]+y[7],c2=y[7]-y[3]; 16 d=(a1*c2-c1*a2)/(a1*b2-b1*a2); 17 e=(c1-b1*d)/a1; 18 xx=x[3]+e*(x[2]-x[3])+d*(x[0]-x[3]),yy=y[3]+e*(y[2]-y[3])+d*(y[0]-y[3]); 19 printf("%.6lf %.6lf\n",xx,yy); 20 } 21 return 0; 22 }
(感觉这个代码有问题的,细节没处理好)
剩下三道先咕咕,L题至今没调出来(……
8.28数据结构场
感觉发挥比上一场稍微好了一些,也可能是因为题目的感觉不一样了
A:杭电多校第二场C++ to Python
题意:读入一个字符串,用(…)代替其中的std::make_tuple(...)
忽略所有的std::make_tuple就好,需要的输出都是括号逗号和数字
不贴代码了
B:杭电多校第四场Climb Stairs
题意:勇士有初始攻击力a0,有n层每层1个怪兽要打败,当攻击力大于等于怪物生命值则可以去这层打败怪兽并将其生命值加到自己的攻击力上。每次可以向上跳不超过k层或向下1层,不能去已经去过的楼层。
向上跳不超过k层,向下1层,说明要么是往上爬一层吃掉怪兽,要么是向上跳不超过k层然后一级一级扫荡下来,然后再跳
处理出前缀和,从前往后扫,找每个怪兽能够被吃掉需要从之后的最近的哪个楼层回来记为maxi,如果能直接吃掉就吃掉并往上走,不能的话就存一下当前需要走到的最远的maxi,当目前走到的位置能够直接跳过去并且大于等于这个maxi,就可以跳过去然后往回全吃掉了。往尽量近的能够处理完前面所有数字的合法位置跳一定比远的合法位置更优。
注意判断最远只能跳k层
1 #include<bits/stdc++.h> 2 using namespace std; 3 const int N=1e6+10,MAX=1e9; 4 int t,n,k,a[N],pos,maxx,flag,tag,a0,r; 5 long long ans,sum[N]; 6 int ask(int x,int l,int r){ 7 if(l>r)return tag=min(tag,MAX); 8 if(l==r){ 9 if(ans+sum[r]-sum[x]>=a[x])return tag=min(tag,r); 10 else return tag=min(tag,MAX); 11 } 12 int mid=(l+r)>>1; 13 if(ans+sum[mid]-sum[x]>=a[x]){ 14 tag=min(tag,mid); 15 tag=min(tag,ask(x,l,mid-1)); 16 } 17 else tag=min(tag,ask(x,mid+1,r)); 18 return tag; 19 } 20 int main() 21 { 22 scanf("%d",&t); 23 while(t--){ 24 scanf("%d%d%d",&n,&a0,&k); 25 ans=a0; 26 for(int i=1;i<=n;i++){ 27 scanf("%d",&a[i]); 28 sum[i]=sum[i-1]+a[i]; 29 } 30 pos=flag=r=0; 31 maxx=MAX; 32 for(int i=1;i<=n;i++){ 33 if(a[i]<=ans){ 34 if(r+1==i){ 35 pos=r=i; 36 ans+=a[i]; 37 } 38 else{ 39 if(maxx<=i){ 40 ans+=sum[i]-sum[r]; 41 maxx=MAX; 42 pos=pos+1; 43 r=i; 44 } 45 } 46 } 47 else{ 48 tag=MAX; 49 int x=ask(i,i,n); 50 if(x>pos+k){ 51 // printf("NO\n"); 52 flag=1; 53 break; 54 } 55 else{ 56 if(maxx==1e9)maxx=x; 57 else maxx=max(maxx,x); 58 } 59 } 60 } 61 if(!flag&&sum[n]+a0==ans)printf("YES\n"); 62 else printf("NO\n"); 63 } 64 return 0; 65 }
C:杭电多校第五场Buy Figurines
题意:n个人买东西,每个人有一个到达时间和需要花费的时间。到达时每个人会选择当前人数最少的队伍,人数最少的队伍有相同则选择编号更小的队伍。同一时刻该走的人走完才会开始选择要排的队伍,问最后一个人离开的时间。
用两个优先队列维护每个队伍的人数和现在队伍最后一个人什么时候走,后一个队列用来辅助维护前一个队列
具体看代码比较好理解
1 #include<bits/stdc++.h> 2 using namespace std; 3 const int N=2e5+10; 4 int t,n,m,num[N]; 5 struct node{ 6 int ar,sp; 7 }a[N]; 8 priority_queue<pair<long long,int> >q1,q; 9 //priority_queue<int>q; 10 long long maxx,tim[N]; 11 bool cmp(node x,node y){ 12 return x.ar<y.ar; 13 } 14 int main() 15 { 16 scanf("%d",&t); 17 while(t--){ 18 scanf("%d%d",&n,&m); 19 maxx=0; 20 for(int i=1;i<=n;i++){ 21 scanf("%d%d",&a[i].ar,&a[i].sp); 22 } 23 sort(a+1,a+n+1,cmp); 24 while(q.size())q.pop(); 25 while(q1.size())q1.pop(); 26 for(int i=1;i<=m;i++)q.push(make_pair(0,-i)),num[i]=tim[i]=0; 27 for(int i=1;i<=n;i++){ 28 while(q1.size()&&-q1.top().first<=a[i].ar){ 29 int x=q1.top().second; 30 q1.pop(); 31 num[x]--; 32 q.push(make_pair(-num[x],-x)); 33 } 34 while(num[-q.top().second]!=-q.top().first&&q.size()){ 35 q.pop(); 36 } 37 if(!q.size())break; 38 int x=-q.top().second; 39 q.pop(); 40 tim[x]=max(a[i].sp+tim[x],(long long)a[i].ar+a[i].sp); 41 q1.push(make_pair(-tim[x],x)); 42 num[x]++; 43 q.push(make_pair(-num[x],-x)); 44 maxx=max(maxx,tim[x]); 45 } 46 printf("%lld\n",maxx); 47 } 48 return 0; 49 }
D:杭电多校第三场Package Delivery
题意:给定n个区间,每个区间给出l和r,可以在一个点上取走最多k个包含这个点的区间,问最少取几次
贪心,从前往后走的话每个区间一定在它的r上取走最优,因为一定要在r及之前拿走,不如拖到最后一点看能不能多拿点
维护优先队列,每个区间的l和r拆开分别入队,遇到l就入队,遇到r就出队最多k个
1 #include<bits/stdc++.h> 2 using namespace std; 3 const int N=1e5+10; 4 int t,n,k,cnt,vis[N],ans; 5 struct node{ 6 int l,r; 7 }a[N]; 8 struct tag{ 9 int op,x,id; 10 }b[N<<1]; 11 priority_queue<pair<int,int> >q; 12 bool cmp(tag x,tag y){ 13 return x.x==y.x?x.op<y.op:x.x<y.x; 14 } 15 int main() 16 { 17 scanf("%d",&t); 18 while(t--){ 19 scanf("%d%d",&n,&k); 20 ans=cnt=0; 21 for(int i=1;i<=n;i++){ 22 scanf("%d%d",&a[i].l,&a[i].r); 23 b[++cnt].op=0,b[cnt].x=a[i].l,b[cnt].id=i; 24 b[++cnt].op=1,b[cnt].x=a[i].r,b[cnt].id=i; 25 vis[i]=0; 26 } 27 sort(b+1,b+cnt+1,cmp); 28 for(int i=1;i<=cnt;i++){ 29 if(b[i].op==0){//左 30 q.push(make_pair(-a[b[i].id].r,b[i].id)); 31 } 32 else{//右 33 if(vis[b[i].id])continue; 34 else{ 35 ans++; 36 vis[b[i].id]=1; 37 int num=k; 38 while(num&&q.size()){ 39 vis[q.top().second]=1; 40 q.pop(); 41 num--; 42 } 43 } 44 } 45 } 46 printf("%d\n",ans); 47 } 48 return 0; 49 }
E:杭电多校第三场Two Permutations
不知道是实现有问题还是方法有问题,到现在也没调出来,拿到的题解和网上的大部分也不太一样,先咕
F:杭电多校第五场Slipper
题意:给出一棵树,根为1,有起点s和终点t,从相连的一个点走到另一个点需要花费边权w的代价,从深度相差k的一个点飞到另一个点需要花费p的代价,问从s到t的最小代价。
虚点最短路。对每层建虚点,拆成入点和出点,入点向这层每个点连边,这层每个点向出点连边,然后出点连向相差k的层的入点,之后跑最短路
如果不拆点则会出现从一个点跑到同层另一个点只需要花0代价的情况
1 #include<bits/stdc++.h> 2 #pragma GCC optimize("Ofast") 3 using namespace std; 4 const int N=3e6+10; 5 int T,n,maxx,k,p,s,t; 6 int dep[1000010]; 7 int tot,Next[6000010],head[3000010],edge[6000010],ver[6000010]; 8 long long dis[3000010]; 9 priority_queue<pair<long long,int> >q; 10 int read(){ 11 char ch=getchar();int r=1,c=0; 12 for(;ch<'0'||ch>'9';ch=getchar())if(ch=='-')r=-1; 13 for(;ch>='0'&&ch<='9';c=c*10+ch-'0',ch=getchar()); 14 return c*r; 15 } 16 void add(int x,int y,int w){ 17 ver[++tot]=y; 18 Next[tot]=head[x]; 19 head[x]=tot; 20 edge[tot]=w; 21 } 22 void dfs(int x,int f){ 23 dep[x]=dep[f]+1; 24 maxx=max(maxx,dep[x]); 25 for(int i=head[x];i;i=Next[i]){ 26 int y=ver[i]; 27 if(y==f)continue; 28 dfs(y,x); 29 } 30 } 31 void dij(){ 32 for(int i=1;i<=n+(maxx<<1);i++)dis[i]=1e18; 33 q.push(make_pair(dis[s]=0,s)); 34 while(q.size()){ 35 while(q.size()&&dis[q.top().second]!=-q.top().first){ 36 q.pop(); 37 } 38 if(!q.size())break; 39 int x=q.top().second; 40 q.pop(); 41 for(int i=head[x];i;i=Next[i]){ 42 int y=ver[i]; 43 if(dis[y]>dis[x]+edge[i]){ 44 dis[y]=dis[x]+edge[i]; 45 q.push(make_pair(-dis[y],y)); 46 } 47 } 48 } 49 } 50 int main() 51 { 52 T=read(); 53 while(T--){ 54 for(int i=1;i<=n+(maxx<<1);i++)head[i]=0; 55 n=read(); 56 maxx=tot=0; 57 for(int i=1,x,y,w;i<n;i++){ 58 x=read(),y=read(),w=read(); 59 add(x,y,w),add(y,x,w); 60 } 61 k=read(),p=read(); 62 s=read(),t=read(); 63 dfs(1,0); 64 for(int i=1;i<=n;i++){ 65 add(i,dep[i]+n+maxx,0),add(dep[i]+n,i,0); 66 } 67 for(int i=1;i+k<=maxx;i++){ 68 add(i+n+maxx,i+n+k,p),add(i+n+k+maxx,i+n,p); 69 } 70 dij(); 71 printf("%lld\n",dis[t]); 72 } 73 return 0; 74 }
G:杭电多校第六场Planar graph
题意:给出一个由n个点m条边构成的平面图,可以在任意边上挖隧道把两个平面连接起来,求最小的把所有平面连接要挖的隧道数,且字典序最小
分析题意,因为题目保证是平面图,发现如果存在环的话环内平面一定不和环外平面联通。题目相当于删边好让图内不存在环。因为要求字典序最小,所以从大到小扫每条边并加入,要求删边数最小所以能加就加,发现加上就是环了那就删。其实就是对每个连通分量求最大生成树
1 #include<iostream> 2 #include<cstdio> 3 using namespace std; 4 const int N=2e5+10; 5 int t,n,m,ans,cnt; 6 int f[N],b[N]; 7 struct node{ 8 int x,y; 9 }a[N]; 10 int get(int x){ 11 if(f[x]==x)return x; 12 else return f[x]=get(f[x]); 13 } 14 int main() 15 { 16 scanf("%d",&t); 17 while(t--){ 18 scanf("%d%d",&n,&m); 19 ans=cnt=0; 20 for(int i=1;i<=n;i++)f[i]=i; 21 for(int i=1;i<=m;i++){ 22 scanf("%d%d",&a[i].x,&a[i].y); 23 } 24 for(int i=m;i>=1;i--){ 25 int fx=get(a[i].x),fy=get(a[i].y); 26 if(fx!=fy){ 27 f[fx]=fy; 28 } 29 else ans++,b[++cnt]=i; 30 } 31 printf("%d\n",ans); 32 for(int i=cnt;i>=1;i--)printf("%d ",b[i]); 33 printf("\n"); 34 } 35 return 0; 36 }
H:杭电多校第七场Counting Stickmen
题意:求树上的火柴人个数。火柴人由头、胸、腹、两臂和两足组成,头胸腹是连成长为3的串的三个点,两臂是胸点连出去的另外两条长为2的串,足是腹点连出去的另外两个长为1的串。
↓如图所示,2、3、5分别是头胸腹,4&6和9&10是两臂,7、8是两足
一开始用了一些很复杂的维护方法,比如维护每个点为胸点/腹点,然后先算头的情况再考虑臂……等等
比较方便的正解是考虑枚举胸和腹之间那条边,维护每个点的度数,假设枚举到树上的x作为胸和它的一个子节点y作为腹,边(x,y)为枚举的胸腹边,那么C(deg(y)-1,1)就是足的方案数。然后先考虑臂的方案的话,对于每种臂的方案,头的方案就是C(deg(x)-3,1)。这样不用去考虑把每个头排除掉后臂的情况,又要考虑选出的头会产生什么影响……之类。
先考虑臂的方案,先不排除两个手长在一条胳膊上的情况(如上图中选5&7和5&8作为两臂),发现令num(x)为所有子节点的son的和,方案数就是C(num(x)-son(y),2)。然后再除去两只手长在一条胳膊上的情况,维护sum(x)为x每个子节点C(deg-1,2)的和,然后对于枚举到的这个作为腹的子节点,要除去的方案数就是sum(x)-C(deg(y)-1,2)。
这是对于x为胸y为腹的情况,然后反过来再算一次就好了。
因为不确定处理顺序的影响写的时候多dfs了一次,应该是没有必要的
1 #include<bits/stdc++.h> 2 using namespace std; 3 const int N=5e5+10,mod=998244353; 4 int t,n,fa[N]; 5 int tot,Next[N<<1],head[N],ver[N<<1],deg[N]; 6 long long ans,sh[N],lo[N],num[N],sum[N],num2[N],son[N],p[N]; 7 long long F[N],inv[N]; 8 long long pw(long long x,int p){ 9 long long num=1; 10 while(p){ 11 if(p&1)num=num*x%mod; 12 x=x*x%mod; 13 p>>=1; 14 } 15 return num; 16 } 17 void pre(int x){ 18 F[0]=0,F[1]=1; 19 for(int i=2;i<=x;i++)F[i]=F[i-1]*i%mod; 20 inv[x]=pw(F[x],mod-2); 21 for(int i=x-1;i>=0;i--)inv[i]=inv[i+1]*(i+1)%mod; 22 } 23 void add(int x,int y){ 24 ver[++tot]=y; 25 Next[tot]=head[x]; 26 head[x]=tot; 27 } 28 long long C(long long n,long long m){ 29 return F[n]*inv[m]%mod*inv[n-m]%mod; 30 } 31 void dfs(int x,int f){ 32 // dep[x]=1; 33 for(int i=head[x];i;i=Next[i]){ 34 int y=ver[i]; 35 if(y==f)continue; 36 deg[x]++; 37 // fa[y]=x; 38 dfs(y,x); 39 // dep[x]=max(dep[x],dep[y]+1); 40 // if(dep[y]<2)sh[x]++; 41 // else lo[x]++; 42 sum[x]=(sum[x]+C(deg[y]-1,2))%mod; 43 num[x]=(num[x]+deg[y]-1)%mod; 44 } 45 deg[x]+=(f!=0); 46 } 47 void dfs1(int x,int f){ 48 if(f){ 49 num[x]=(num[x]+deg[f]-1)%mod; 50 sum[x]=(sum[x]+C(deg[f]-1,2))%mod; 51 } 52 for(int i=head[x];i;i=Next[i]){ 53 int y=ver[i]; 54 if(y==f)continue; 55 // ans=(ans+C(deg[y]-1,2)*C(deg[x]-3,1)%mod*(C(num[x]-(deg[y-1]),2)-(sum[x]-C(deg[y]-1,2)))%mod)%mod; 56 // ans=(ans+C(deg[x]-1,2)*C(deg[y]-3,1)%mod*(C(num[y]-(deg[x-1]),2)-(sum[y]-C(deg[x]-1,2)))%mod)%mod; 57 dfs1(y,x); 58 } 59 } 60 void dfs2(int x,int f){ 61 for(int i=head[x];i;i=Next[i]){ 62 int y=ver[i]; 63 if(y==f)continue; 64 ans=(ans+C(deg[y]-1,2)*C(deg[x]-3,1)%mod*(C(num[x]-(deg[y]-1),2)-(sum[x]-C(deg[y]-1,2)))%mod)%mod; 65 ans=(ans+C(deg[x]-1,2)*C(deg[y]-3,1)%mod*(C(num[y]-(deg[x]-1),2)-(sum[y]-C(deg[x]-1,2)))%mod)%mod; 66 dfs2(y,x); 67 } 68 } 69 int main() 70 { 71 scanf("%d",&t); 72 pre(N-10); 73 while(t--){ 74 scanf("%d",&n); 75 ans=tot=0; 76 for(int i=1;i<=n;i++){ 77 deg[i]=num[i]=sum[i]=p[i]=head[i]=0; 78 } 79 for(int i=1,x,y;i<n;i++){ 80 scanf("%d%d",&x,&y); 81 add(x,y),add(y,x); 82 } 83 dfs(1,0); 84 dfs1(1,0); 85 dfs2(1,0); 86 printf("%lld\n",ans); 87 } 88 return 0; 89 }
剩下两道没看,桃跑.jpg
8.30人类智慧场
人类智慧场,但关我这种没有智慧的人什么事呢.jpg
这场没有翻译了,因为英语太差被创死了
A:杭电多校第八场Stormwind
题意:给一个矩形n*m,要求画平行边的横竖的线来分割矩形,使分割后每一块面积不小于k,问最多画多少线
分割长宽其中之一,用最小的分割段长*没分割的那一边>=k作为一种答案,再用分割另一边分割出的最小段长*这一边>=k作为另一种答案
优先分割一边不会不优于横竖同时分割,比较分割n和分割m的答案
1 #include<bits/stdc++.h> 2 using namespace std; 3 int t,n,m,k; 4 int num1,num2,sum1,sum2; 5 long long ans1,ans2; 6 int main() 7 { 8 scanf("%d",&t); 9 while(t--){ 10 scanf("%d%d%d",&n,&m,&k); 11 for(int i=1;i<=m;i++){ 12 if(i*n>=k){ 13 num1=i; 14 break; 15 } 16 } 17 sum1=num1*n/k; 18 ans1=(m/num1)-1+sum1-1; 19 for(int i=1;i<=n;i++){ 20 if(i*m>=k){ 21 num2=i; 22 break; 23 } 24 } 25 sum2=num2*m/k; 26 ans2=(n/num2)-1+sum2-1; 27 printf("%lld\n",max(ans1,ans2)); 28 } 29 return 0; 30 }
(懒了,找最小段长用了个枚举)
B:杭电多校第一场Alice and Bob
看到Alice和Bob就会呼吸停滞那么几ms
题意:有m个写在黑板上的数字,每个数字在0-n之间。如果数字个数>1,Alice可以进行把所有数字分成两组的操作,Bob可以选择把其中一组数全部消掉,并将另一组数全部-1。任何时候如果黑板上出现了数字0,则Alice胜利。如果所有数字都被消掉了,则Bob赢。给出黑板上一开始的所有数,问谁能获胜。
一开始我考虑当一个数字i有大于等于2^i个时,只要Alice每次平分,Bob一定是消不完的。后来发现从小到大,就算一种数字不够2^i个,它也可以用来帮后面的数字凑个数,Bob不得不要考虑消掉它们。
从后往前,每个数字i的个数a[i]等价于a[i]/2个i-1,一直处理下去看有没有a[0]
1 #include<bits/stdc++.h> 2 using namespace std; 3 const int N=1e6+10; 4 int t,n,flag; 5 long long num,a[N]; 6 int main() 7 { 8 scanf("%d",&t); 9 while(t--){ 10 scanf("%d",&n); 11 for(int i=0;i<=n;i++)scanf("%lld",&a[i]); 12 num=1; 13 flag=0; 14 for(int i=n;i>=1;i--){ 15 a[i-1]+=a[i]/2; 16 } 17 if(a[0])printf("Alice\n"); 18 else printf("Bob\n"); 19 } 20 return 0; 21 }
C: 杭电多校第九场Matryoshka Doll
题意:有n个套娃,当两个套娃的大小a[i]-a[j]>=r,这两个套娃可以被套在一起。问有多少种方式把n个套娃分成k组,使每组的套娃都可以套成一个。
*a[i]单调不降*
把n个套娃分成k组,每组间差值>=r。动态规划,设dp[i][j]表示处理到第i个,已经分成j组。首先dp[i][j]可以由dp[i-1][j-1]转移来,表示第i个单独成组。之后考虑dp[i][j]要怎么从dp[i-1][j]转移来,要如何把第i个放到j个组内。
第i个不能和a[i]-a[k]<r的前面的第k个放在一组,而因为a数组单调不降,这样的a[k]相互之间的差也不会>=r。也就是说,不能和i放在一组的k们一定分布在不同的组内且每组只有一个,每个占据一组。那么找到第一个a[i]-a[l]>=r的l,i-l-1就是不能放的组数,j-(i-l-1)就是能放的组数。
dp[i][j]=dp[i-1][j-1]+dp[i-1][j]*(j-(i-l-1)),初始值为dp[0][0]=1
1 #include<bits/stdc++.h> 2 using namespace std; 3 const int N=5e3+10,mod=998244353; 4 int t,n,k,r; 5 int a[N],p[N]; 6 long long dp[N][N]; 7 int main() 8 { 9 scanf("%d",&t); 10 while(t--){ 11 scanf("%d%d%d",&n,&k,&r); 12 for(int i=1;i<=n;i++){ 13 scanf("%d",&a[i]); 14 p[i]=upper_bound(a+1,a+i+1,a[i]-r)-a-1; 15 } 16 dp[0][0]=1; 17 for(int i=1;i<=n;i++){ 18 for(int j=1;j<=k;j++){ 19 dp[i][j]=(dp[i-1][j-1]+dp[i-1][j]*max((j-(i-p[i]-1)),0)%mod)%mod; 20 } 21 } 22 printf("%lld\n",dp[n][k]); 23 } 24 return 0; 25 }
D:杭电多校第八场Darnassus
题意:给出一个排列p[i],表示每个i有权值p[i],i和j之间连边的边权为|i-j|*|p[i]-p[j]|,求最小生成树
首先考虑相邻点连边的这种情况,因为p是1到n的排列,发现每条边都是|p[i+1]-p[i]|<n,也就是说最后答案中的最小生成树最大的边不会超过n
那么找出所有满足|i-j|*|p[i]-p[j]|<n的边,跑最小生成树
要满足上面的不等式,|i-j|和|p[i]-p[j]|其中一定有一个小于等于√n。把p和位置映射存一下,然后枚举差值不超√n的情况,看是否满足边权小于n,满足条件就加入可以最小生成树的边集。(具体见代码)
1 #include<bits/stdc++.h> 2 using namespace std; 3 const int N=5e4+10; 4 int t,n,p[N],a[N],f[N],cnt; 5 int head[N],tot,X[23000000],Y[23000000],Next[23000000]; 6 long long ans; 7 int get(int x){ 8 if(x==f[x])return x; 9 else return f[x]=get(f[x]); 10 } 11 int main(){ 12 scanf("%d",&t); 13 while(t--){ 14 ans=tot=0; 15 scanf("%d",&n); 16 for(int i=1;i<=n;i++){ 17 scanf("%d",&p[i]); 18 a[p[i]]=i; 19 f[i]=i; 20 head[i]=0; 21 } 22 for(int i=1;i<n;i++){ 23 for(int j=1;j*j<=n&&j+i<=n;j++){ 24 long long x=abs(i-(i+j))*abs(p[i]-p[i+j]); 25 if(x<n){ 26 Next[++tot]=head[x]; 27 head[x]=tot; 28 X[tot]=i,Y[tot]=i+j; 29 } 30 x=abs(i-(i+j))*abs(a[i]-a[i+j]); 31 if(x<n){ 32 Next[++tot]=head[x]; 33 head[x]=tot; 34 X[tot]=a[i],Y[tot]=a[i+j]; 35 } 36 } 37 } 38 cnt=1; 39 for(int i=1;i<n;i++){ 40 for(int j=head[i];j;j=Next[j]){ 41 int x=X[j],y=Y[j],w=i; 42 int xx=get(x),yy=get(y); 43 if(xx!=yy){ 44 f[xx]=yy; 45 ans+=w; 46 cnt++; 47 } 48 if(cnt==n)break; 49 } 50 } 51 printf("%lld\n",ans); 52 } 53 return 0; 54 }
E:杭电多校第七场Independent Feedback Vertex Set
题意:初始有三个点1、2、3,两两连边组成一个三角形。后续会加入到共n个点,每个点加入时向已经存在的两个点连边,组成新的三元环。然后要把所有点分成两个集合,一个集合是独立集(所有点之间没有连边),另一个集合是森林。每个点都有权值,求最大的独立集的点权和。
令每个点黑色代表选进独立集,白色代表不选。考虑每个三元环,如果已经有两个白点,那第三个点一定是黑点(森林里没有环);如果已经有一黑一白,那第三个点一定是白点(独立集内点不连边);如果有两黑,那一定不合法了。
所有三元环都由一开始1、2、3的三元环拓展而来,拓展的方式和上面染色的考虑过程相符。那么整张图的情况就由最开始的三元环决定,枚举一下这个三元环的情况,然后比较最大的答案。
1 #include<iostream> 2 #include<cstdio> 3 using namespace std; 4 const int N=1e5+10; 5 int t,n,a[N],b[N]; 6 long long ans,num; 7 struct node{ 8 int x,y; 9 }c[N]; 10 int main() 11 { 12 scanf("%d",&t); 13 while(t--){ 14 ans=0; 15 scanf("%d",&n); 16 for(int i=1;i<=n;i++)scanf("%d",&a[i]); 17 for(int i=4;i<=n;i++){ 18 scanf("%d%d",&c[i].x,&c[i].y); 19 } 20 for(int i=1;i<=3;i++){ 21 b[1]=b[2]=b[3]=0; 22 b[i]=1,num=a[i]; 23 for(int j=4;j<=n;j++){ 24 if(b[c[j].x]==1&&b[c[j].y]==1){ 25 num=0; 26 break; 27 } 28 else if(b[c[j].x]|b[c[j].y])b[j]=0; 29 else b[j]=1,num+=a[j]; 30 } 31 ans=max(ans,num); 32 } 33 printf("%lld\n",ans); 34 } 35 return 0; 36 }
F:杭电多校第二场Snatch Groceries
题意:(有用的只有最后两段)读入n个区间l,r,当区间有重叠的时候后续的区间都不计数,问有多少有效不重叠区间。
比赛的时候因为读不懂题面被狠狠创死了,不认识terminate这个词(……)
读懂以后就简单了,排序一下,重叠就停止
1 #include<bits/stdc++.h> 2 using namespace std; 3 const int N=1e5+10; 4 int t,n,cnt,ans,flag; 5 struct node{ 6 int l,r; 7 }a[N]; 8 bool cmp(node x,node y){ 9 return x.l<y.l; 10 } 11 queue<pair<node,int> >q; 12 int main() 13 { 14 scanf("%d",&t); 15 while(t--){ 16 scanf("%d",&n); 17 ans=0; 18 for(int i=1;i<=n;i++){ 19 scanf("%d%d",&a[i].l,&a[i].r); 20 } 21 sort(a+1,a+n+1,cmp); 22 a[n+1].l=1e9+10; 23 a[0].r=-1; 24 for(int i=1;i<=n;i++){ 25 if(a[i].r>=a[i+1].l||a[i].l<=a[i-1].r)break; 26 else ans++; 27 } 28 printf("%d\n",ans); 29 } 30 return 0; 31 }
G:杭电多校第五场Bragging Dice
题意:太复杂了,贴个原题面https://acm.hdu.edu.cn/showproblem.php?pid=7194
两个人已知骰子的情况,那么只要说个数最多且点数最大的先手就一定能赢。但如果先手一开始什么都说不了,就是先手输。一开始的声明只能说有骰子个数大于等于1的情况,而根据特殊规则会存在两个杯子各自所有骰子各不相同相当于没有骰子的情况。这种时候就是先手输了。
1 #include<bits/stdc++.h> 2 using namespace std; 3 const int N=2e5+10; 4 int t,n,a[N],b[N],cnt[10],flag,num[10],maxx,tag; 5 int main(){ 6 scanf("%d",&t); 7 while(t--){ 8 for(int i=1;i<=6;i++)cnt[i]=num[i]=0; 9 scanf("%d",&n); 10 flag=0; 11 for(int i=1;i<=n;i++){ 12 scanf("%d",&a[i]); 13 cnt[a[i]]++; 14 if(cnt[a[i]]>=2)flag=1; 15 } 16 if(!flag){ 17 for(int i=1;i<=n;i++){ 18 cnt[a[i]]--; 19 } 20 } 21 else if(cnt[a[1]]==n){ 22 cnt[a[1]]++; 23 } 24 flag=0; 25 for(int i=1;i<=n;i++){ 26 scanf("%d",&b[i]); 27 num[b[i]]++; 28 if(num[b[i]]>=2)flag=1; 29 } 30 if(!flag){ 31 for(int i=1;i<=n;i++){ 32 cnt[b[i]]--; 33 } 34 } 35 else if(cnt[b[1]]==n){ 36 cnt[b[1]]++; 37 } 38 maxx=tag=0; 39 for(int i=1;i<=6;i++){ 40 cnt[i]+=num[i]; 41 if(cnt[i]>=maxx){ 42 maxx=cnt[i]; 43 tag=i; 44 } 45 } 46 if(!maxx){ 47 printf("Just a game of chance.\n"); 48 } 49 else printf("Win!\n"); 50 } 51 return 0; 52 }
H:杭电多校第六场Loop
题意:给出一个序列,可以进行k次操作,每次操作可以选择序列的一个子串,将左端点移到右端点,其它数字向前一个位置。求k次操作后能得到的最大字典序的序列。
赛场上使用了一个错误的贪心,但是因为数据水还是过了。我能确定的思路就到分别寻找lr,复杂度爆炸,因此开始考虑一些奇怪的方法。猜测对于每个数字它的前k位都是合法范围,维护优先队列选择当前k+1个最大的数字。对拍了一下随机数据没拍出错,盲猜题目数据和随机一样水,试着交了一下就过了。
赛后被hack了,这个方法没有考虑到k是会被消耗的,对于中间的过程也没想清楚。一次轮换相当于拿起最前端的数字,然后任意时刻放下。正确贪心是取出当前的k个后判断最大的数前面的数要拿走几个,从k中扣去,再考虑接下来k+1个数的最大值和现在已经拿起的最大值。每次轮换一定拿起一个数,每个数要被拿起也要消耗到最前端的距离+1个轮换。k扣去的数量和拿起前面多少个数一致。
代码还没来得及改,咕
I:杭电多校第十场Wavy Tree
题意:给定一个长度为n的序列,每次操作可以把其中的一个元素+1或者-1。问最少多少次操作以后能使这个序列满足对于任意i,a[i]>a[i-1]&a[i]>a[i+1]或者a[i]<a[i-1]&a[i]<a[i+1]。
问多少次操作后能呈波浪形,先分成两种情况,偶数位置大于两边奇数位置或者奇数位置大于两边偶数位置。对于每一种确定的目标情况,贪心求解。
对于每一个位置,只需要和上一个位置比较看是否合法,而不用从每一个点开始考虑要比两边大或者小,这样能操作最少次。
1 #include<bits/stdc++.h> 2 using namespace std; 3 const int N=1e6+10; 4 int t,n,flag,a[N],b[N]; 5 long long ans1,ans2; 6 int main() 7 { 8 scanf("%d",&t); 9 while(t--){ 10 scanf("%d",&n); 11 ans1=ans2=0; 12 for(int i=1;i<=n;i++)scanf("%d",&a[i]); 13 for(int i=1;i<n;i++){ 14 b[i]=a[i+1]-a[i]; 15 } 16 for(int i=1;i<n;i++){ 17 if(i&1){ 18 if(b[i]>=0){ 19 ans1+=b[i]-(-1); 20 b[i+1]+=(b[i]-(-1)); 21 } 22 } 23 else{ 24 if(b[i]<=0){ 25 ans1+=(1-b[i]); 26 b[i+1]-=(1-b[i]); 27 } 28 } 29 } 30 for(int i=1;i<n;i++){ 31 b[i]=a[i+1]-a[i]; 32 } 33 for(int i=1;i<n;i++){ 34 if(!(i&1)){ 35 if(b[i]>=0){ 36 ans2+=b[i]-(-1); 37 b[i+1]+=(b[i]-(-1)); 38 } 39 } 40 else{ 41 if(b[i]<=0){ 42 ans2+=(1-b[i]); 43 b[i+1]-=(1-b[i]); 44 } 45 } 46 } 47 printf("%lld\n",min(ans1,ans2)); 48 } 49 return 0; 50 }
后面的题没看,咕了
选拔赛被大佬吊打,没有信心没有结构没有智慧
写完题解总结的一刻感受到一阵沧桑(并没有)