SWJTU校队训练&Codeforces&Atcoder&&牛客三月补题
目录:
1.CodeCraft-20 (Div. 2) 3.4
2.Codeforces Round #626 (Div2) 3.7
3.2020 SWJTU-ICPC Training Round #1(13年浙江省赛) 3.8
4. Educational Codeforces Round 83 (Div2)3.9
5.Codeforces Round #627 (Div. 3) 3.12
6.2020 SWJTU-ICPC Training Round #2(18年福建省赛)部分题解 3.15
7.Codeforces Global Round7 3.19
8.牛客小白月赛23 3.21
9.Codeforces Round #629 (Div. 3) 3.26
1.CodeCraft-20 (Div. 2) 3.4
链接:https://codeforces.com/contest/1316
E. Team Building(状压DP)
题意:
有n个人,每个人作为观众有其能力值,作为球员打不同的位置也有不同的能力值,现在要选q个位置不同球员跟m名观众使得能力值总和最大
思路:
由于p很小,所以我们首先想到状压DP,二进制上第i个位置表示是否已经选了第i种球员
首先贪心一下,把他们按观众价值从大到小排序,所以在观众人数还没满的情况下如果不当球员就一定会当观众
首先:dp[i][sta] = dp[i-1][sta]继承上一个人的状态
当观众:观众还有余量的时 dp[i][sta]=max(dp[i][sta],dp[i-1][sta]+a[i].a);
当球员:踢第j个位置,则这个状态必须在第j个有球员 dp[i][sta]=max(dp[i][sta],dp[i-1][ (1<<j)]+people.p[j])
#include<iostream> #include<algorithm> #include<cstring> #define inf 1e18 using namespace std; typedef long long ll; const int maxn=1e5+10; struct node{ ll a; ll s[8]; }a[maxn]; ll dp[maxn][1<<8]; int cmp(node a,node b){return a.a>b.a;} int cal_num(int x) { int ans=0; while(x){ if(x&1) ans++; x>>=1; } return ans; } int main() { int n,p,k; scanf("%d%d%d",&n,&p,&k); for(int i=1;i<=n;i++) scanf("%lld",&a[i].a); for(int i=1;i<=n;i++) for(int j=0;j<p;j++) scanf("%lld",&a[i].s[j]); sort(a+1,a+1+n,cmp); for(int sta=0;sta<(1<<p);sta++) dp[0][sta]=-inf; dp[0][0]=0; int len=(1<<p)-1; for(int i=1;i<=n;i++){ for(int j=0;j<=len;j++){ dp[i][j]=dp[i-1][j]; if(i-cal_num(j)<=k) dp[i][j]=max(dp[i][j],dp[i-1][j]+a[i].a); for(int l=0;l<p;l++){ if(j&(1<<l)) dp[i][j]=max(dp[i][j],dp[i-1][j^(1<<l)]+a[i].s[l]); } } } cout<<dp[n][(1<<p)-1]<<endl; }
2.Codeforces Round #626 (Div2) 3.7
链接:https://codeforces.com/contest/1323
D. Present(位运算+二分)
题意:
给一个数组,求(a1+a2)^(a1+a3)^...(a1+an)^(a2+ a3)^...(a2+an)....^(an-1+an)
思路:
可以看出答案中每一位的贡献都是独立的,所以我们计算每一位的贡献
对于第i位,因为i位之后的数对第i位不会产生影响,所以我们分别对a[i]%(1<<(i+1)),取模之后的任意两数和不会超过2i+2-2
还有一个很显而易见的事就是任意两数和第i位上为1的个数如果是奇数的话第i位就会对答案产生贡献,所以我们就只要查找有多少对符合要求的数
如果两数之和在[2i , 2i+1) 与 [2i+1+2i , 2i+2-2] 这两个区间之内,第i位上就会为1
之后就是一个比较经典的二分问题了,就是对于一个数找有多少个符合相加满足一定条件的数的个数,对于每个数二分一下即可
#include<iostream> #include<algorithm> using namespace std; const int maxn=4e5+10; int a[maxn],b[maxn]; int main() { int n,ans=0; scanf("%d",&n); for(int i=1;i<=n;i++) scanf("%d",&a[i]); for(int i=0;i<=24;i++){ int cnt=0; for(int j=1;j<=n;j++) b[j]=a[j]%(1<<(i+1)); sort(b+1,b+1+n); for(int j=1;j<=n;j++){ int l=lower_bound(b+1,b+1+n,(1<<i)-b[j])-b; l=max(l,j+1); int r=lower_bound(b+1,b+1+n,(1<<(i+1))-b[j])-b; r=max(r,j+1); cnt+=(r-l); } for(int j=1;j<=n;j++){ int l=lower_bound(b+1,b+1+n,(1<<i)+(1<<(i+1))-b[j])-b; l=max(l,j+1); int r=n+1; cnt+=(r-l); } if(cnt%2) ans+=1<<i; } cout<<ans<<endl; return 0; }
3.2020 SWJTU-ICPC Training Round #1(13年浙江省赛) 3.8
链接:https://vjudge.net/contest/360406#rank
K-Highway Project(最短路)
题意:
给一副无向图每条边有价值与距离,让你求出0到各个点的最短路,并且造价最小
思路:
直接跑Spfa,如果可以进行松弛操作则松弛,因为首先要求距离最短,然后如果在松弛时距离相同,则取价格更小的哪条
#include<iostream> #include<algorithm> #include<cstring> #include<vector> #include<queue> #define inf 0x3f3f3f3f using namespace std; typedef long long ll; const int maxn=1e5+10; ll val[maxn],dis[maxn],vis[maxn]; struct node{ int to; ll x,y; node(int a,ll b,ll c):to(a),x(b),y(c){} }; vector<vector<node> >a; void init() { a.clear(); memset(val,0,sizeof(val)); memset(dis,inf,sizeof(dis)); memset(vis,0,sizeof(vis)); } void Spfa() { queue<int> q; dis[0]=val[0]=0; vis[0]=1; q.push(0); while(!q.empty()){ int u=q.front(); q.pop(); vis[u]=0; for(int i=0;i<a[u].size();i++){ int v=a[u][i].to; if(v==u) continue; int tim=a[u][i].x; int cost=a[u][i].y; if(dis[v]>dis[u]+tim){ dis[v]=dis[u]+tim; val[v]=cost; if(!vis[v]){ vis[v]=1; q.push(v); } } else if(dis[v]==dis[u]+tim&&val[v]>cost) val[v]=cost; } } } int main() { int t,n,m,u,v,x,y; ll ans1=0,ans2=0; scanf("%d",&t); while(t--){ init(); ans1=ans2=0; scanf("%d%d",&n,&m); a.resize(n+10); for(int i=1;i<=m;i++){ scanf("%d%d%d%d",&u,&v,&x,&y); a[u].push_back(node(v,x,y)); a[v].push_back(node(u,x,y)); } Spfa(); for(int i=1;i<n;i++){ ans1+=val[i]; ans2+=dis[i]; } cout<<ans2<<" "<<ans1<<endl; } return 0; }
4. Educational Codeforces Round 83 (Div2)3.9
链接:https://codeforces.com/contest/1312
D. Count the Arrays (组合数学)
题意:
给定n,m,让你输出有多少个数组满足:1.长度为n 2.可选1-m范围的数,且必须有一对相同的数 3.数组先递增后非严格递减
思路:
m种数字选n-1种 第一步自然就是组合数先求出来C(m,n-1)
然后 我们考虑 重复的数字,因为峰值只能有一个 那么重复的数字一定不是最大值, 所以重复的数字的选择有n-2种
那么对于种类确定了之后,根据题意可以推断出来,重复的数字一定是在峰值的左右个一个,那么去掉这三个数后,还有n-3个数字 并且种类两两不同
对于这n-3个数 也可以说n-3种数,要么他在峰值的左边 要么在右边,其实就是2^(n-3) 种分配方式 不是左就是右 类比二进制位不是0就是1
所以答案就是C(m,n-1)*(n-2)*2^(n-3)
#include<iostream> #include<algorithm> #define mod 998244353 using namespace std; typedef long long ll; inline ll read(){ll s=0,w=1;char ch=getchar(); while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();} while(ch>='0'&&ch<='9') s=s*10+ch-'0',ch=getchar(); return s*w;} ll quick_mod(ll a,ll b) { ll ans = 1; while(b){ if(b&1){ ans = (ans*a)%mod; --b; } a = (a*a)%mod; b >>= 1; } return ans%mod; } ll Inv(ll x){ return quick_mod(x,mod-2); } ll C(ll n,ll m){ if (m>n) return 0; ll ans = 1; for (int i = 1; i <= m; ++i) ans=ans*Inv(i)%mod*(n-i+1)%mod; return ans%mod; } int main() { ll n=read(),m=read(),ans=0; ans=C(m,n-1); ans%=mod; ans=ans*(quick_mod(2,n-3))%mod*(n-2)%mod; ans%=mod; cout<<(ans+mod)%mod<<endl; return 0; }
E. Array Shrinking(区间DP)
题意:
给一个n个数的数组,如果左右两个数相同,可以将两个数合并为x+1,问最短可以将数组缩短至多长
思路:
先进行一次区间DP,dp[i][j]表示可以将数组中[i,]]的位置缩成什么数,可以通过区间DP直接处理出来
之后进行线性DP,f[i]表示可以将[1,i]缩短至多长,然后由于前i个数都分好了,我们只需要取min f[k]+1( 满足k<i且[k+1,i]可以合成一个数 ) 即可
#include<iostream> #include<algorithm> using namespace std; const int maxn=505; int dp[maxn][maxn],f[maxn],a[maxn]; //dp[i][j]表示区间可以合并成什么数,f[i]表示1-i能合并成几个 int main() { int n; scanf("%d",&n); for(int i=1;i<=n;i++) scanf("%d",&a[i]); for(int i=1;i<=n;i++) dp[i][i]=a[i],f[i]=i;; for(int len=2;len<=n;len++){//区间长度 for(int l=1;l+len-1<=n;l++){//区间起点 int r=l+len-1;//区间重点 for(int k=l;k<r;k++){//间隔点 if(dp[l][k]==dp[k+1][r]&&dp[l][k]) dp[l][r]=dp[l][k]+1; } } } for(int i=1;i<=n;i++){ if(dp[1][i]){ f[i]=1; continue; } for(int j=2;j<=i;j++){ if(dp[j][i]) f[i]=min(f[i],f[j-1]+1); } } cout<<f[n]<<endl; }
5.Codeforces Round #627 (Div. 3) 3.12
链接:https://codeforces.com/contest/1324
E. Sleeping Schedule(DP)
题意:
一天有h个小时有个人要睡n次,每次会在上一次醒来之后ai小时或者ai-1小时之后再睡,如果入睡的时间在[l , r]范围之内就为一次优质的睡眠,现在问他最多能有几次优质睡眠
思路:
类似于01背包,但这里不是睡与不睡而是选择在哪一个时间点睡,直接转移就好了
#include<iostream> #include<algorithm> #include<cstring> using namespace std; const int maxn=2e3+10; int a[maxn],dp[maxn][maxn]; int main() { int n,h,l,r,tim,ans=0; scanf("%d%d%d%d",&n,&h,&l,&r); for(int i=1;i<=n;i++) scanf("%d",&a[i]); memset(dp,-1,sizeof(dp)); dp[0][0]=0; for(int i=1;i<=n;i++){ for(int j=0;j<h;j++){ if(dp[i-1][j]!=-1){ tim=(j+a[i]-1)%h; dp[i][tim]=max(dp[i][tim],dp[i-1][j]+(l<=tim&&tim<=r)); tim=(j+a[i])%h; dp[i][tim]=max(dp[i][tim],dp[i-1][j]+(l<=tim&&tim<=r)); } } } for(int i=0;i<h;i++) ans=max(ans,dp[n][i]); cout<<ans; return 0; }
F. Maximum White Subtree(换根DP)
题意:
给定n个点,及各个点的颜色,还有n-1条边,保证连边成树/无根树,求各个点所在的连通子图里面最大的cnt白-cnt黑
思路:
由于是无根树,所以考虑换根DP,先进行第一遍dfs求出以1号节点为根的答案,DP1[i] = a[i] + ∑ max( 0 , DP1[v] ) ,其中v为i号节点的儿子
之后开始换根,i号节点的答案可以从其父亲推出
1、当前子树(包含自己)如果是正的,直接取,否则就只取当前点
2、除了当前子树以外(必须包含父节点)如果是正的也要取,负的就不取
递推式为:DP2[v] = DP1[v] + max(0, DP2[x] - max(0, DP1[v] ) )
DP2[x] - max(0, DP1[v] )是因为如果DP1[v] 如果小于0的话,那么在第一遍dfs时肯定没有加入到DP2[x]中,所以不能减去
#include<iostream> #include<algorithm> #include<vector> using namespace std; const int maxn=2e5+10; int dp1[maxn],dp2[maxn],c[maxn]; vector<int> a[maxn]; void dfs1(int x,int fa) { dp1[x]=c[x]; for(int i=0;i<a[x].size();i++){ int v=a[x][i]; if(v==fa) continue; dfs1(v,x); dp1[x]+=max(0,dp1[v]); } } void dfs2(int x,int fa) { for(int i=0;i<a[x].size();i++){ int v=a[x][i]; if(v==fa) continue; dp2[v]=dp1[v]+max(0,dp2[x]-max(0,dp1[v])); dfs2(v,x); } } int main() { int n,u,v; scanf("%d",&n); for(int i=1;i<=n;i++){ scanf("%d",&c[i]); if(!c[i]) c[i]=-1; } for(int i=1;i<n;i++){ scanf("%d%d",&u,&v); a[u].push_back(v); a[v].push_back(u); } dfs1(1,1); dp2[1]=dp1[1]; dfs2(1,1); for(int i=1;i<=n;i++) cout<<dp2[i]<<" "; return 0; }
6.2020 SWJTU-ICPC Training Round #2(18年福建省赛)部分题解 3.15
题解链接:https://www.cnblogs.com/overrate-wsj/p/12499890.html
7.Codeforces Global Round7 3.19
链接:https://codeforces.com/contest/1326
D2. Prefix-Suffix Palindrome (Hard version)(Hash)
题意:
给一个字符串,让你选一个前缀跟后缀相连构成一个新的字符串使得新的字符串为回文串并且长度不超过原来的字符串
思路:
先对前缀与后缀匹配回文,直到适配,然后再在前缀之后的部分找一个回文串或者在后缀之前找一个回文串
然后就是一个判断回文串的过程,时间复杂度只能为O(N)或者为O(logN)
我们选择用hash来判断,先O(N)处理出前缀的哈希跟后缀的哈希,之后要判断一段是否为回文串只需判断,前缀的哈希跟后缀的哈希是否相同即可,单次时间复杂度为O(1)
#include<iostream> #include<algorithm> #include<cstring> using namespace std; typedef long long ll; const ll mod=999998639; const int base=2333; const int maxn=1e6+10; ll pre[maxn],suf[maxn],po[maxn]; char s[maxn]; ll ha1(ll l,ll r) { return (pre[r]-pre[l-1]*po[r-l+1]%mod+mod)%mod; } ll ha2(ll l,ll r) { return (suf[l]-suf[r+1]*po[r-l+1]%mod+mod)%mod; } int main() { int t; scanf("%d",&t); while(t--){ cin>> s+1; int n=strlen(s+1); po[0]=1,pre[0]=suf[n+1]=0; for(int i=1;i<=n;i++) po[i]=po[i-1]*base%mod; for(int i=1;i<=n;i++) pre[i]=(pre[i-1]*base+s[i]-'a'+1)%mod; for(int i=n;i>=1;i--) suf[i]=(suf[i+1]*base+s[i]-'a'+1)%mod; int l=0,r=0,x=0; while(s[x+1]==s[n-x]&&x+1<n-x) x++; for(int i=x+1;i<=n-x;i++) if(ha1(x+1,i)==ha2(x+1,i)) l=i-x; for(int i=n-x-1;i>=x+1;i--) if(ha1(i,n-x)==ha2(i,n-x)) r=n-x-i+1; for(int i=1;i<=x;i++) cout<<s[i]; if(l>=r) for(int i=x+1;i<=x+l;i++) cout<<s[i]; else for(int i=n-x;i>=n-x-r+1;i--) cout<<s[i]; for(int i=x;i>=1;i--) cout<<s[i]; cout<<endl; } return 0; }
8.牛客小白月赛23 3.21
链接:https://ac.nowcoder.com/acm/contest/4784#question
C.完全图(等差数列+二分)
题意:
给一个n个顶点的完全图,问删去m条边后最多会剩多少个连通分量(n,m≤1e18)
思路:
很容易想到,最优的方法是每次将一个顶点从原来的图中分离,需要的花费为(n-x) x为第几个被移出的顶点
由于n很大,枚举是不可能的,但是通过观察可以发现删除所需要的花费为一个等差数列,所以要移出x个节点的最少花费就为((n-1)+(n-x))*x/2
因此答案就为 ((n-1)+(n-x))*x/2<=m的最小正整数解再加一,由于该不等不能直接求解,因此我们可以通过二分答案的方式来求得
#include<iostream> #include<algorithm> using namespace std; typedef unsigned long long ull; typedef long long ll; int main() { int t; scanf("%d",&t); while(t--){ ull n,m; cin>>n>>m; __int128 l=0,r=n-1,ans; while(l<=r) { __int128 mid=(l+r)/2; if((n-1+n-mid)*mid/2<=m) { ans=mid; l=mid+1; } else r=mid-1; } ll tmp=(ll)(ans+1); cout<<tmp<<endl; } }
G-树上求和
题意:
给一颗树,让你对每一条边赋值[1,n-1]且每条边权值不同,使得所有路径的权值和最小
思路:
很明显,被经过次数越多的边的权值就得越少,每条边被经过的次数就为边两边点的个数的乘积
我们就进行一遍dfs,求出以1为根每颗子树的大小,每条边被经过的次数就为siz[x]*(n-siz[x]) x为两个节点中深度更大的节点
然后我们就其进行排序再乘上权值即可
#include<iostream> #include<algorithm> #include<vector> #include<queue> using namespace std; typedef long long ll; const int maxn=1e5+10; ll siz[maxn],n; ll num[maxn],ans[maxn]; vector<int> a[maxn]; void dfs(int x,int fa) { siz[x]=1; for(int i=0;i<a[x].size();i++){ int u=a[x][i]; if(u==fa) continue; dfs(u,x); siz[x]+=siz[u]; } num[x]=siz[x]*(n-siz[x]); } int main() { int u,v; scanf("%d",&n); for(int i=1;i<n;i++){ scanf("%d%d",&u,&v); a[u].push_back(v); a[v].push_back(u); } dfs(1,0); sort(num+2,num+1+n); reverse(num+2,num+1+n); ll ret=0,cnt=0; for(int i=2;i<=n;i++) ret+=num[i]*(++cnt); cout<<ret; return 0; }
H-奇怪的背包问题增加了
题意:
有一个背包的容量为230,现在有m个物品,每个物品的体积为2a[i],问你是否有一种方案能将背包装满
思路:
直接按体积从大到小排序,如果能往背包里装就往里装,最后判断剩余容量是否为0即可
#include<iostream> #include<algorithm> using namespace std; typedef long long ll; const int maxn=1e5+10; struct node{ ll val,pos; }a[maxn]; int cmp(node a,node b){return a.val>b.val;} ll b[maxn]; int main() { int t,n,x; scanf("%d",&t); while(t--){ scanf("%d",&n); ll sum=1ll<<30; for(int i=1;i<=n;i++){ scanf("%d",&x); a[i].val=1ll<<x; a[i].pos=i; b[i]=0; } sort(a+1,a+1+n,cmp); for(int i=1;i<=n;i++){ if(sum-a[i].val>=0){ sum-=a[i].val; b[a[i].pos]=1; } } if(sum) cout<<"impossible"<<endl; else{ for(int i=1;i<=n;i++) cout<<b[i]; cout<<endl; } } return 0; }
9.Codeforces Round #629 (Div. 3) 3.26
链接:https://codeforces.ml/contest/1328
D. Carousel
题意:
n个动物围成一圈,要求给所有动物染色,要求相邻的不同种类的动物需要不同的颜色
思路:
分成以下几种情况讨论
①所有动物都是同一种类,那么就只需要一种颜色
②n为偶数: 只需要两种颜色,每种颜色相间排列即可
③n为奇数: i.有相邻的两个动物为同种类,那么就将他们染成一个颜色,这样就会变成n为偶数的情况
ii.所有动物都不一样,将1- n-1个动物按照1.2.1.2...染色,最后一个染成3即可
#include<iostream> #include<algorithm> #include<vector> #include<set> using namespace std; const int maxn=2e5+10; int a[maxn],ans[maxn]; set<int> s; int main() { int t,n; scanf("%d",&t); while(t--){ bool ok=false; s.clear(); scanf("%d",&n); for(int i=1;i<=n;i++){ scanf("%d",&a[i]); if(a[i]==a[i-1]) ok=true; s.insert(a[i]); } if(a[n]==a[1]) ok=true; if(s.size()==1){ cout<<1<<endl; for(int i=1;i<=n;i++) cout<<1<<" "; cout<<endl; continue; } if(n%2==0){ cout<<2<<endl; for(int i=1;i<=n;i++) cout<<i%2+1<<" "; cout<<endl; continue; } else{ if(ok){ int flag=1; cout<<2<<endl; for(int i=1;i<=n;i++){ if(!flag){ if(ans[i-1]==1) ans[i]=2; else ans[i]=1; cout<<ans[i]<<" "; } else{ if(flag&&a[i]==a[i-1]){ cout<<(i-1)%2+1<<" "; flag=0; ans[i]=(i-1)%2+1; } else cout<<i%2+1<<" ",ans[i]=i%2+1; } } cout<<endl; continue; } cout<<3<<endl; for(int i=1;i<n;i++) cout<<i%2+1<<" "; cout<<3<<endl; } } return 0; }
E.Tree Queries(LCA)
题意:
给一颗树,m个询问,每次询问给出k个点,问所有点是否在一条链上或者距离链的距离为1
思路:
先求出所有点的深度以及父亲节点,每次询问将所有点按照深度排序
之后将所有节点变为其父亲节点,然后将所有a[i]与a[i+1]点求LCA,如果LCA不为a[i]则不行
#include<bits/stdc++.h> using namespace std; const int maxn=2e5+10; struct node{ int t,nex; }e[maxn*2]; int depht[maxn],father[maxn][22],lg[maxn],head[maxn],tot,a[maxn]; int cmp(int a,int b){return depht[a]>depht[b];} inline void add(int x,int y) { e[++tot].t=y; e[tot].nex=head[x]; head[x]=tot; } inline void dfs(int now,int fath) { depht[now]=depht[fath]+1; father[now][0]=fath; for(register int i=1;(1<<i)<=depht[now];++i) father[now][i]=father[father[now][i-1]][i-1]; for(register int i=head[now];i;i=e[i].nex) { if(e[i].t!=fath)dfs(e[i].t,now); } } inline int lca(int x,int y) { if(depht[x]<depht[y]) swap(x,y); while(depht[x]>depht[y]) x=father[x][lg[depht[x]-depht[y]]-1]; if(x==y) return x; for(register int k=lg[depht[x]];k>=0;--k) if(father[x][k]!=father[y][k]) x=father[x][k],y=father[y][k]; return father[x][0]; } int main() { int n,m,cnt,x; scanf("%d%d",&n,&m); for(register int i=1;i<=n-1;++i) { int x,y; scanf("%d%d",&x,&y); add(x,y); add(y,x); } dfs(1,1); for(register int i=1;i<=n;++i) lg[i]=lg[i-1]+(1<<lg[i-1]==i); for(int i=1;i<=m;i++){ scanf("%d",&cnt); for(int j=1;j<=cnt;j++){ scanf("%d",&a[j]); } int flag=0; sort(a+1,a+1+cnt,cmp); for(int j=1;j<cnt;j++){ int k1=father[a[j]][0],k2=father[a[j+1]][0]; if(depht[k1]==depht[k2]&&k1!=k2) flag=1; if(lca(k1,k2)!=k2) flag=1; if(flag){ cout<<"NO"<<endl; break; } } if(!flag) cout<<"YES"<<endl; } return 0; }