目录
- 前言
- 题解部分
- B Ichihime and Triangle(800)
- 题目大意
- 题解
- 代码实现
- C Kana and Dragon Quest game(900)
- 题目大意
- 题解
- 代码实现
- J Squares and Cubes(800)
- 题目大意
- 题解
- 代码实现
- F Double Sort(1200)
- 题目大意
- 题解
- 代码实现
- I Minimize the Thickness(1100)
- 题目大意
- 题解
- 代码实现
- K Find the Spruce(1400)
- 题目大意
- 题解
- 代码实现
- E Xenia and Colorful Gems(1700)
- 题目大意
- 题解
- 代码实现
- L Floor and Mod(1700)
- 题目大意
- 题解
- 代码实现
- M Divide and Summarize(1600)
- 题目大意
- 题解
- 代码实现
- D Linova and Kingdom(1600)
- 题目大意
- 题解
- 代码实现
- G Permutation Restoration(1900)
- 题目大意
- 题解
- 代码实现
- A Card Game(1500)
- 题目大意
- 题解
- 代码实现
- H Maximum AND(1800)
- 题目大意
- 题解
- 代码实现
前言
感谢 shstyle 为本场训练赛挑选题目
以笔者作为参与者的个人视角,本场题目难度如下(题目颜色采用luogu分级,数字为cf题目评分)
Easy:BCJ
Mid:FIK
Mid hard:ELMD
Hard:GAH
题解部分
B Ichihime and Triangle(800)
原题链接:https://codeforces.com/problemset/problem/1337/A
题目大意
给定四个数 a a a, b b b, c c c, d d d,输出三个数 x x x, y y y, z z z使
a ≤ x ≤ b , b ≤ y ≤ c , c ≤ z ≤ d a \leq x \leq b,b \leq y \leq c,c \leq z \leq d a≤x≤b,b≤y≤c,c≤z≤d且能组成以 x x x, y y y, z z z为边的三角形,保证一定有解
题解
#include<bits/stdc++.h>
#define INF 2147483647LL
#define int long long
#define MAXN 300005
#define mod 1000000007
#define PI 3.14
#define eps 1e-10
#define pa pair<int,int>
#define ms(a,x) memset(a,x,sizeof(a))
#define mc(ar1,ar2) memcpy(ar1,ar2,sizeof(ar2))
#define mkp(a,b) make_pair(a,b)
#define ls (p<<1)
#define rs (p<<1|1)
#define fn(i,st,ed) for(int i=st;i<=ed;++i)
#define fd(i,st,ed) for(int i=st;i>=ed;--i)
#define fg(i,x,head,e) for(int i=head[x];~i;i=e[i].nxt)
using namespace std;
inline int read(){int x=0,f=1;char c=getchar();while(!isdigit(c)){if(c=='-')f=-1;c=getchar();}while(isdigit(c)){x=x*10+c-'0';c=getchar();}return x*f;}
void solve(){int a,b,c,d;cin>>a>>b>>c>>d;cout<<b<<' '<<c<<' '<<c<<endl;
}
signed main(){ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);int T;cin>>T;while(T--){solve();}return 0;
}
代码实现
关键点在于保证一定有解,又因为三角形两边之和大于第三边,两边之差小于第三边,所以较小的 x x x和 y y y取上边界,较大的 z z z取下边界即是正解
时间复杂度 O ( 1 ) O(1) O(1)
C Kana and Dragon Quest game(900)
原题链接:https://codeforces.com/problemset/problem/1337/B
题目大意
给定数字 x x x, n n n, m m m,现在有两种操作,一种是使 x = ⌊ x 2 ⌋ + 10 x= \left\lfloor\dfrac{x}{2}\right\rfloor+10 x=⌊2x⌋+10,最多能进行 n n n次该操作,一种是使 x = x − 10 x=x-10 x=x−10,最多能进行 m m m次该操作,问是否能通过上述操作使得 x ≤ 0 x\leq0 x≤0
题解
显然对于操作 1 1 1, x x x先减后增,不难求出阈值点是 x = 20 x=20 x=20,而操作 2 2 2是单调减的,所以可以先将操作 1 1 1尽量做,达到阈值之后进行操作 2 2 2,模拟上述过程,最后判断即可
时间复杂度 O ( n + m ) O(n+m) O(n+m)
代码实现
#include<bits/stdc++.h>
#define INF 2147483647LL
#define int long long
#define MAXN 300005
#define mod 1000000007
#define PI 3.14
#define eps 1e-10
#define pa pair<int,int>
#define ms(a,x) memset(a,x,sizeof(a))
#define mc(ar1,ar2) memcpy(ar1,ar2,sizeof(ar2))
#define mkp(a,b) make_pair(a,b)
#define ls (p<<1)
#define rs (p<<1|1)
#define fn(i,st,ed) for(int i=st;i<=ed;++i)
#define fd(i,st,ed) for(int i=st;i>=ed;--i)
#define fg(i,x,head,e) for(int i=head[x];~i;i=e[i].nxt)
using namespace std;
inline int read(){int x=0,f=1;char c=getchar();while(!isdigit(c)){if(c=='-')f=-1;c=getchar();}while(isdigit(c)){x=x*10+c-'0';c=getchar();}return x*f;}
void solve(){int x,n,m;cin>>x>>n>>m;while(x>=20&&n)x=(x>>1)+10,n--;while(x>=0&&m)x-=10,m--;if(x<=0)cout<<"YES"<<endl;else cout<<"NO"<<endl;
}
signed main(){ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);int T;cin>>T;while(T--){solve();}return 0;
}
J Squares and Cubes(800)
原题链接:https://codeforces.com/problemset/problem/1619/B
题目大意
输出小于等于 n n n的完全平方数和完全立方数的数量和
题解
容斥原理,答案输出 n + n 3 − n 6 \sqrt{n}+\sqrt[3]{n}-\sqrt[6]{n} n+3n−6n,立方根可以用 n 3 \sqrt[3]{n} 3n预处理,也可以使用pow函数,且pow函数更为简洁,但是会需要自行调整精度
时间复杂度 O ( n 3 ) O(\sqrt[3]{n}) O(3n)或 O ( 1 ) O(1) O(1)
代码实现
#include<bits/stdc++.h>
#define INF 2147483647LL
#define int long long
#define MAXN 300005
#define mod 1000000007
#define PI 3.14
#define eps 1e-10
#define pa pair<int,int>
#define ms(a,x) memset(a,x,sizeof(a))
#define mc(ar1,ar2) memcpy(ar1,ar2,sizeof(ar2))
#define mkp(a,b) make_pair(a,b)
#define ls (p<<1)
#define rs (p<<1|1)
#define fn(i,st,ed) for(int i=st;i<=ed;++i)
#define fd(i,st,ed) for(int i=st;i>=ed;--i)
#define fg(i,x,head,e) for(int i=head[x];~i;i=e[i].nxt)
using namespace std;
inline int read(){int x=0,f=1;char c=getchar();while(!isdigit(c)){if(c=='-')f=-1;c=getchar();}while(isdigit(c)){x=x*10+c-'0';c=getchar();}return x*f;}
void solve(){double n;cin>>n;n+=0.00001;cout<<(int)pow(n,1.0/2)+(int)pow(n,1.0/3)-(int)pow(n,1.0/6)<<endl;
}
signed main(){ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);int T;cin>>T;while(T--){solve();}return 0;
}
F Double Sort(1200)
原题链接:https://codeforces.com/problemset/problem/1681/C
题目大意
给定两个长度均为 n n n的序列 a a a和 b b b,可以对任意 a i a_i ai, a j a_j aj ( i ≠ j ) (i\neq j) (i=j) 进行交换,同时交换 b i b_i bi, b j b_j bj,询问是否能通过不超过 1 0 4 10^4 104次交换使得 a a a和 b b b全部从小到大排列,输出需要进行的操作数量以及需要进行的操作,若有多组答案,输出任意一组,若无法达到,则输出 − 1 -1 −1
题解
先考虑若必定有答案时的答案方案,由 n ≤ 100 n\leq100 n≤100可知 n 2 ≤ 1 0 4 n^2\leq10^4 n2≤104,而冒泡排序的操作次数最多为 n ( n − 1 ) 2 \frac{n(n-1)}{2} 2n(n−1)次,所以只要输出冒泡排序的交换次数和双方即可
再考虑什么情况下无法达到,由冒泡排序过程可知,若任意一次交换中 a a a和 b b b对应数对大小关系不相同,则无法达到要求,输出 − 1 -1 −1
最终时间复杂度 O ( n 2 ) O(n^2) O(n2)
代码实现
#include<bits/stdc++.h>
#define INF 2147483647LL
#define int long long
#define MAXN 300005
#define mod 1000000007
#define PI 3.14
#define eps 1e-10
#define pa pair<int,int>
#define ms(a,x) memset(a,x,sizeof(a))
#define mc(ar1,ar2) memcpy(ar1,ar2,sizeof(ar2))
#define mkp(a,b) make_pair(a,b)
#define ls (p<<1)
#define rs (p<<1|1)
#define fn(i,st,ed) for(int i=st;i<=ed;++i)
#define fd(i,st,ed) for(int i=st;i>=ed;--i)
#define fg(i,x,head,e) for(int i=head[x];~i;i=e[i].nxt)
using namespace std;
inline int read(){int x=0,f=1;char c=getchar();while(!isdigit(c)){if(c=='-')f=-1;c=getchar();}while(isdigit(c)){x=x*10+c-'0';c=getchar();}return x*f;}
struct node{int a,b,id;}a[MAXN],stp[MAXN];
bool cmp(node x,node y){return x.a==y.a?x.b<y.b:x.a<y.a;}
void solve(){int n,ans=0;cin>>n;fn(i,1,n)cin>>a[i].a,a[i].id=i;fn(i,1,n)cin>>a[i].b;sort(a+1,a+1+n,cmp);fn(i,1,n-1)if(a[i].a>a[i+1].a||a[i].b>a[i+1].b){cout<<-1<<endl;return;}fn(i,1,n-1)fn(j,i+1,n){if(a[i].id>a[j].id){swap(a[i],a[j]);stp[++ans]={i,j};}}if(ans>10000){cout<<-1<<endl;return;}cout<<ans<<endl;fd(i,ans,1)cout<<stp[i].a<<" "<<stp[i].b<<endl;
}
signed main(){ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);int T;cin>>T;while(T--){solve();}return 0;
}
I Minimize the Thickness(1100)
原题链接:https://codeforces.com/problemset/problem/1741/C
题目大意
给定长度为 n n n的序列 a a a,将 a a a分为若干个和相同的连续子序列,求最大子序列的最小值
题解
显然,对于任意分割状态,第一个连续子序列必定以 a 1 a_1 a1为第一个元素,可以枚举第一个子序列的长度 l e n len len,然后向后延伸,检查后面是否能恰好分割出若干个和为 s u m [ l e n ] sum[len] sum[len]的子序列( s u m sum sum为前缀和),若能则与答案取最小值
时间复杂度 O ( n ) O(n) O(n)
代码实现
#include<bits/stdc++.h>
#define INF 2147483647LL
#define int long long
#define MAXN 300005
#define mod 1000000007
#define PI 3.14
#define eps 1e-10
#define pa pair<int,int>
#define ms(a,x) memset(a,x,sizeof(a))
#define mc(ar1,ar2) memcpy(ar1,ar2,sizeof(ar2))
#define mkp(a,b) make_pair(a,b)
#define ls (p<<1)
#define rs (p<<1|1)
#define fn(i,st,ed) for(int i=st;i<=ed;++i)
#define fd(i,st,ed) for(int i=st;i>=ed;--i)
#define fg(i,x,head,e) for(int i=head[x];~i;i=e[i].nxt)
using namespace std;
inline int read(){int x=0,f=1;char c=getchar();while(!isdigit(c)){if(c=='-')f=-1;c=getchar();}while(isdigit(c)){x=x*10+c-'0';c=getchar();}return x*f;}
int a[MAXN],sum[MAXN];
void solve(){int n,now=0,ans;cin>>n;ans=n;fn(i,1,n)cin>>a[i],sum[i]=sum[i-1]+a[i];fn(i,1,n){int tmp=i,nowsum=0,len=0;fn(j,i+1,n){nowsum+=a[j],len++;if(nowsum==sum[i]){//cerr<<now<<endl;tmp=max(tmp,len),nowsum=0,len=0;continue;}if(nowsum>sum[i]){nowsum=-1;break;}}if(nowsum<sum[i]&&nowsum!=0)continue;if(nowsum!=-1)ans=min(ans,tmp);}cout<<ans<<endl;
}
signed main(){ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);int T;cin>>T;while(T--){solve();}return 0;
}
K Find the Spruce(1400)
原题链接:https://codeforces.com/problemset/problem/1461/B
题目大意
查询一张 n × m n\times m n×m大小的图上有多少由“ * ”组成的如下图的等腰三角形,可以重叠,“ * ”也是一个答案
题解
观察图像可知所有等腰三角形都是由一个“ * ”向下向外延伸,纵向向下一格,横向向两个方向各扩展一格,所以可以 n m nm nm枚举每个点,对每个点进行延伸,每延伸一次答案就+1,对于横行是否合法可以维护前缀和来判断
时间复杂度 O ( n 2 m ) O(n^2m) O(n2m)
代码实现
#include<bits/stdc++.h>
#define INF 2147483647LL
#define int long long
#define MAXN 300005
#define mod 1000000007
#define PI 3.14
#define eps 1e-10
#define pa pair<int,int>
#define ms(a,x) memset(a,x,sizeof(a))
#define mc(ar1,ar2) memcpy(ar1,ar2,sizeof(ar2))
#define mkp(a,b) make_pair(a,b)
#define ls (p<<1)
#define rs (p<<1|1)
#define fn(i,st,ed) for(int i=st;i<=ed;++i)
#define fd(i,st,ed) for(int i=st;i>=ed;--i)
#define fg(i,x,head,e) for(int i=head[x];~i;i=e[i].nxt)
using namespace std;
inline int read(){int x=0,f=1;char c=getchar();while(!isdigit(c)){if(c=='-')f=-1;c=getchar();}while(isdigit(c)){x=x*10+c-'0';c=getchar();}return x*f;}
string mp[505];
int sum[505][505];
bool check(int x,int l,int r){if(sum[x][r]-sum[x][l-1]!=(r-l+1))return 0;return 1;
}
void solve(){int n,m;cin>>n>>m;fn(i,1,n)cin>>mp[i];fn(i,1,n)fn(j,0,m-1)sum[i][j]=sum[i][j-1]+(mp[i][j]=='*');int ans=0;fn(i,1,n)fn(j,0,m-1)if(mp[i][j]=='*'){ans++;int maxx=1,maxy=1;while(i+maxx<=n&&j-maxy>=0&&j+maxy<m&&check(i+maxx,j-maxy,j+maxy))ans++,maxx++,maxy++;}cout<<ans<<endl;
}
signed main(){ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);int T;cin>>T;while(T--){solve();}return 0;
}
E Xenia and Colorful Gems(1700)
原题链接:https://codeforces.com/problemset/problem/1336/B
题目大意
现在有三堆石头,分别有 n r n_r nr, n g n_g ng, n b n_b nb个,每个石头有自己的价值,从每一堆中拿出一个石头,记价值分别为 x x x, y y y, z z z,求 ( x − y ) 2 + ( x − z ) 2 + ( y − z ) 2 (x-y)^2+(x-z)^2+(y-z)^2 (x−y)2+(x−z)2+(y−z)2的最小值
题解
不妨设 x ≤ y ≤ z x\leq y\leq z x≤y≤z,若已知中间值 y y y,则可以通过lowerbound轻松求出 x x x所在数组小于等于 y y y的最大值 x x x与 y y y所在数组大于等于 y y y的最小值 z z z,所以只需要确定数组顺序和中间值,这里可以对序列的顺序进行排列,然后对中间序列进行遍历,对每个 y y y求出答案取最小值,值得思考的是怎么优化代码长度,这里推荐使用指针参数或者数组参数缩短代码
时间复杂度 O ( n l o g n ) O(nlogn) O(nlogn)
代码实现
#include<bits/stdc++.h>
#define INF 9223372036854775807LL
#define int long long
#define MAXN 300005
#define mod 1000000007
#define PI 3.14
#define eps 1e-10
#define pa pair<int,int>
#define ms(a,x) memset(a,x,sizeof(a))
#define mc(ar1,ar2) memcpy(ar1,ar2,sizeof(ar2))
#define mkp(a,b) make_pair(a,b)
#define ls (p<<1)
#define rs (p<<1|1)
#define fn(i,st,ed) for(int i=st;i<=ed;++i)
#define fd(i,st,ed) for(int i=st;i>=ed;--i)
#define fg(i,x,head,e) for(int i=head[x];~i;i=e[i].nxt)
using namespace std;
inline int read(){int x=0,f=1;char c=getchar();while(!isdigit(c)){if(c=='-')f=-1;c=getchar();}while(isdigit(c)){x=x*10+c-'0';c=getchar();}return x*f;}
bool cmp(int x,int y){return x<y;}
int r[MAXN],g[MAXN],b[MAXN],ans;
int val(int x,int y,int z){return (x-y)*(x-y)+(x-z)*(x-z)+(y-z)*(y-z);}
int find(int *rr,int *gg,int *bb,int nrr,int ngg,int nbb){int res=INF,l=0,r=0;fn(i,1,nrr){while(l<=ngg&&gg[l]<=rr[i])l++;while(r<=nbb&&bb[r]<rr[i])r++;if(l!=1&&r!=nbb+1)res=min(res,val(rr[i],gg[l-1],bb[r]));}return res;
}
void solve(){ans=INF;int nr,ng,nb;cin>>nr>>ng>>nb;fn(i,1,nr)cin>>r[i];sort(r+1,r+nr+1,cmp);fn(i,1,ng)cin>>g[i];sort(g+1,g+ng+1,cmp);fn(i,1,nb)cin>>b[i];sort(b+1,b+nb+1,cmp);ans=min(ans,find(r,g,b,nr,ng,nb));ans=min(ans,find(r,b,g,nr,nb,ng));ans=min(ans,find(g,r,b,ng,nr,nb));ans=min(ans,find(g,b,r,ng,nb,nr));ans=min(ans,find(b,r,g,nb,nr,ng));ans=min(ans,find(b,g,r,nb,ng,nr));cout<<ans<<endl;return;
}
signed main(){ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);int T;cin>>T;while(T--){solve();}return 0;
}
L Floor and Mod(1700)
原题链接:https://codeforces.com/problemset/problem/1485/C
题目大意
给定 x x x, y y y,问满足 1 ≤ a ≤ x 1\leq a\leq x 1≤a≤x, 1 ≤ b ≤ y 1\leq b\leq y 1≤b≤y, ⌊ a b ⌋ = a m o d b \left\lfloor\dfrac{a}{b}\right\rfloor=a \bmod b ⌊ba⌋=amodb的 ( a , b ) (a,b) (a,b)对数
题解
本题解法很多,这里只叙述我的做法,根据 ⌊ a b ⌋ = a m o d b \left\lfloor\dfrac{a}{b}\right\rfloor=a \bmod b ⌊ba⌋=amodb可知,若设 k = ⌊ a b ⌋ = a m o d b k=\left\lfloor\dfrac{a}{b}\right\rfloor=a \bmod b k=⌊ba⌋=amodb,则有 a = k b + k a=kb+k a=kb+k,且 k ≤ a k\leq \sqrt{a} k≤a,所以可以枚举 k k k,对于每个 k k k答案就是与此时对应的最大对数,即 a a a和 b b b的最小值再去掉 k k k对,用数学语言表达就是
a n s = Σ k = 1 a m a x ( 0 , m i n ( x − k k , y ) − k ) ans=\Sigma_{k=1}^{\sqrt{a}}max(0,min(\frac{x-k}{k},y)-k) ans=Σk=1amax(0,min(kx−k,y)−k)
时间复杂度 O ( a ) O(\sqrt{a}) O(a)
代码实现
#include<bits/stdc++.h>
#define INF 2147483647LL
#define int long long
#define MAXN 300005
#define mod 1000000007
#define PI 3.14
#define eps 1e-10
#define pa pair<int,int>
#define ms(a,x) memset(a,x,sizeof(a))
#define mc(ar1,ar2) memcpy(ar1,ar2,sizeof(ar2))
#define mkp(a,b) make_pair(a,b)
#define ls (p<<1)
#define rs (p<<1|1)
#define fn(i,st,ed) for(int i=st;i<=ed;++i)
#define fd(i,st,ed) for(int i=st;i>=ed;--i)
#define fg(i,x,head,e) for(int i=head[x];~i;i=e[i].nxt)
using namespace std;
inline int read(){int x=0,f=1;char c=getchar();while(!isdigit(c)){if(c=='-')f=-1;c=getchar();}while(isdigit(c)){x=x*10+c-'0';c=getchar();}return x*f;}
void solve(){int x,y,ans=0;cin>>x>>y;fn(i,1,sqrt(x))ans+=max(0LL,min(y,(x-i)/i)-i);cout<<ans<<endl;
}
signed main(){ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);int T;cin>>T;while(T--){solve();}return 0;
}
M Divide and Summarize(1600)
原题链接:https://codeforces.com/problemset/problem/1461/D
题目大意
给定一长度为 n n n的序列 a a a,进行以下操作:
1. 1. 1.找到序列最大值 M M M和最小值 m m m,记 m i d = ⌊ M + m 2 ⌋ mid=\left\lfloor\dfrac{M+m}{2}\right\rfloor mid=⌊2M+m⌋
2. 2. 2.将原序列中小于等于 m i d mid mid的元素放入左序列,,大于 m i d mid mid的题放入右序列,任意舍弃其中之一,并对留下的序列继续进行上述操作
询问对于每个输入的 x x x,是否在上述操作中有可能有序列和为 x x x
题解
不难发现,题目操作实际是在模拟快排的过程,但是题目解法和快排原理关系不大,可以发现, 1 ≤ a i ≤ 1 0 6 1\leq a_i\leq 10^6 1≤ai≤106,因此序列数量最多不会超过 1 0 6 l o g ( 1 0 6 ) 10^6log(10^6) 106log(106)个,并且递归次数不会超过 l o g ( 1 0 5 ) log(10^5) log(105)次,所以从时间和空间角度不会超时或者爆空间,那么我们只需要模拟题目中的操作分治,每次对当前序列求和并记入map或set等数据结构,最后对每次询问进行判断即可
时间复杂度 O ( n l o g n ) O(nlogn) O(nlogn)
代码实现
#include<bits/stdc++.h>
#define INF 2147483647LL
#define int long long
#define MAXN 300005
#define mod 1000000007
#define PI 3.14
#define eps 1e-10
#define pa pair<int,int>
#define ms(a,x) memset(a,x,sizeof(a))
#define mc(ar1,ar2) memcpy(ar1,ar2,sizeof(ar2))
#define mkp(a,b) make_pair(a,b)
#define ls (p<<1)
#define rs (p<<1|1)
#define fn(i,st,ed) for(int i=st;i<=ed;++i)
#define fd(i,st,ed) for(int i=st;i>=ed;--i)
#define fg(i,x,head,e) for(int i=head[x];~i;i=e[i].nxt)
using namespace std;
inline int read(){int x=0,f=1;char c=getchar();while(!isdigit(c)){if(c=='-')f=-1;c=getchar();}while(isdigit(c)){x=x*10+c-'0';c=getchar();}return x*f;}
int a[MAXN],sum[MAXN];
set<int>ans;
void dfs(int l,int r){if(l>r)return;ans.insert(sum[r]-sum[l-1]);int mid=a[r]+a[l]>>1;int id=upper_bound(a+l,a+r+1,mid)-a;if(id>r)return;dfs(l,id-1);dfs(id,r);
}
void solve(){int n,q;cin>>n>>q;ans.clear();fn(i,1,n)cin>>a[i];sort(a+1,a+1+n);fn(i,1,n)sum[i]=sum[i-1]+a[i];ans.insert(sum[n]);dfs(1,n);while(q--){int x;cin>>x;if(*ans.lower_bound(x)==x)cout<<"Yes"<<endl;else cout<<"No"<<endl;}
}
signed main(){ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);int T;cin>>T;while(T--){solve();}return 0;
}
D Linova and Kingdom(1600)
原题链接:https://codeforces.com/problemset/problem/1336/A
题目大意
给定一棵有 n n n个点的树,标记树上任意 k k k个点,价值为每个被标记点到根路径上标记点数量之和,求价值的最大值
题解
对于树上某点,到达根的路径唯一,不难想出,若一个点被选,则他子树上的所有点都会被选,以下为证明
若某点被选,但子树中有点未被选,因为子树中点的深度一定比字数的根大,因此选择子树中的点不可能比选择子树的根更劣,所以若一个点被选,那么他子树上所有点都已经被选
得出该结论后不难得出对于一个点,若选择这个点,则会产生的 d e p i dep_i depi的贡献,但是由于会侵占子树一个为标记点,所以子树每个点贡献 − 1 -1 −1,所以对于一个点 i i i,其产生的贡献就是 d e p i − s i z i dep_i-siz_i depi−sizi,将得到的所有答案存入堆,取前k个求和即可
代码实现
#include<bits/stdc++.h>
#define INF 2147483647LL
#define int long long
#define MAXN 300005
#define mod 1000000007
#define PI 3.14
#define eps 1e-10
#define pa pair<int,int>
#define ms(a,x) memset(a,x,sizeof(a))
#define mc(ar1,ar2) memcpy(ar1,ar2,sizeof(ar2))
#define mkp(a,b) make_pair(a,b)
#define ls (p<<1)
#define rs (p<<1|1)
#define fn(i,st,ed) for(int i=st;i<=ed;++i)
#define fd(i,st,ed) for(int i=st;i>=ed;--i)
#define fg(i,x,head,e) for(int i=head[x];~i;i=e[i].nxt)
using namespace std;
inline int read(){int x=0,f=1;char c=getchar();while(!isdigit(c)){if(c=='-')f=-1;c=getchar();}while(isdigit(c)){x=x*10+c-'0';c=getchar();}return x*f;}
struct edge{int to,nxt;}e[MAXN<<1];
int head[MAXN],ecnt=-1,dep[MAXN],siz[MAXN];
void insert(int x,int y){e[++ecnt]={y,head[x]};head[x]=ecnt;}
priority_queue<int,vector<int>,less<int> >heap;
void dfs(int x,int fa){dep[x]=dep[fa]+1,siz[x]=1;fg(i,x,head,e){int to=e[i].to;if(to==fa)continue;dfs(to,x);siz[x]+=siz[to];}heap.push(dep[x]-siz[x]);
}
void solve(){ms(head,-1);int n,k,ans=0;cin>>n>>k;fn(i,1,n-1){int x,y;cin>>x>>y;insert(x,y);insert(y,x);}dfs(1,0);fn(i,1,k){ans+=heap.top();heap.pop();}cout<<ans<<endl;
}
signed main(){ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);int T=1;while(T--){solve();}return 0;
}
G Permutation Restoration(1900)
原题链接:https://codeforces.com/problemset/problem/1701/D
题目大意
给定序列长度为 n n n的 b i b_i bi,要求还原原排列 a i a_i ai,满足 b i = ⌊ i a i ⌋ b_i=\left\lfloor\dfrac{i}{a_i}\right\rfloor bi=⌊aii⌋
题解
由题目中所给式子,可以推出 a i a_i ai的取值范围,即 b i ≤ i a i b_i\leq \frac{i}{a_i} bi≤aii ⇒ a i ≤ i b i \Rightarrow a_i\leq\frac{i}{b_i} ⇒ai≤bii b i + 1 > i a i b_i+1>\frac{i}{a_i} bi+1>aii ⇒ a i > i b i + 1 \Rightarrow a_i>\frac{i}{b_i+1} ⇒ai>bi+1i ∴ i b i + 1 < a i ≤ i b i \therefore \frac{i}{b_i+1}<a_i\leq \frac{i}{b_i} ∴bi+1i<ai≤bii
由此,题目转换为尝试求出不相同且满足所有 a i a_i ai条件的 a i a_i ai,不难发现,题目已经转换为经典的线段覆盖问题,我们可以对左端点进行排序,然后按照右端点从小到大塞入堆中,依次取出合法 a i a_i ai即可
代码实现
#include<bits/stdc++.h>
#define INF 2147483647LL
#define int long long
#define MAXN 500005
#define mod 1000000007
#define PI 3.14
#define eps 1e-10
#define pa pair<int,int>
#define ms(a,x) memset(a,x,sizeof(a))
#define mc(ar1,ar2) memcpy(ar1,ar2,sizeof(ar2))
#define mkp(a,b) make_pair(a,b)
#define ls (p<<1)
#define rs (p<<1|1)
#define fn(i,st,ed) for(int i=st;i<=ed;++i)
#define fd(i,st,ed) for(int i=st;i>=ed;--i)
#define fg(i,x,head,e) for(int i=head[x];~i;i=e[i].nxt)
using namespace std;
inline int read(){int x=0,f=1;char c=getchar();while(!isdigit(c)){if(c=='-')f=-1;c=getchar();}while(isdigit(c)){x=x*10+c-'0';c=getchar();}return x*f;}
struct seg{int l,r,id;}s[MAXN];
bool cmp(seg a,seg b){return a.l<b.l;}
int a[MAXN],ans[MAXN];
void solve(){priority_queue<pa,vector<pa>,greater<pa> >heap;int n,num=1;cin>>n;fn(i,1,n){cin>>a[i];s[i]={i/(a[i]+1)+1,a[i]==0?(n+1):i/a[i],i};}sort(s+1,s+n+1,cmp);fn(i,1,n){while(num<=n&&s[num].l==i){heap.push({(s[num].r==n+1)?n:s[num].r,s[num].id});num++;}ans[heap.top().second]=i;heap.pop();}fn(i,1,n)cout<<ans[i]<<' ';cout<<endl;
}
signed main(){ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);int T;cin>>T;while(T--){solve();}return 0;
}
A Card Game(1500)
原题链接:https://codeforces.com/problemset/problem/1739/C
题目大意
游戏牌堆中含偶数 n n n张牌,每张牌上的数字不同,且大小在 1 1 1到 n n n之间(即给定 1 1 1到 n n n的全排列牌,又称 permutation)。两名玩家 A、B 都会在开局分得牌堆中的 n 2 \frac{n}{2} 2n张牌,手牌互异,点数随机。 首先玩家 A 出牌,对手 B 应牌(对手应牌点数需比出牌者大,且丢弃),然后玩家 B 出牌,对手 A 应牌,依次轮转,直至一人无法应牌判定其为输(无更大牌可应),或双方手牌为空判定平局(双方皆空手无牌可出)。 给定牌的张数 n n n,求能使 A 获胜的发牌方式数、能使 B 获胜的发牌方式数、能使双方平局的发牌方式数。
题解
首先考虑平局方案,若是A拿到点数为 n n n的牌,则A可以打出该牌并直接获得胜利,所以B必须获得 n n n,而如果B同时获得了点数为 n − 1 n-1 n−1的牌,则B此时打出该牌会直接获得胜利,因此A必须获得 n − 1 n-1 n−1,以此类推,平局方案只有一种,两人交错获得
然后考虑先手必胜态从 i − 2 i-2 i−2张牌向 i i i张牌转移的方案转换,当这样转移的时候牌堆中会多两张牌 i − 1 i-1 i−1和 i i i,讨论A和B对牌的获得情况
若A同时获得 i − 1 i-1 i−1和 i i i,则可以直接打出 i i i获得胜利,转移方案数为 C i i 2 − 2 C_{i}^{\frac{i}{2}-2} Ci2i−2
若A获得 i i i,B获得 i − 1 i-1 i−1,A仍然可以直接打出 i i i获得胜利,转移方案为 C i i 2 − 1 C_{i}^{\frac{i}{2}-1} Ci2i−1
若A获得 i − 1 i-1 i−1,B获得 i i i,则A打出 i − 1 i-1 i−1,B打出 i i i应牌,此时状态转移为有 i − 2 i-2 i−2张牌时的后手必胜态
若B获得两张牌,则此时A必败
综上所述,先手必胜态转移方程如下 a i = C i i 2 − 2 + C i i 2 − 1 + b i − 2 a_i=C_{i}^{\frac{i}{2}-2}+C_{i}^{\frac{i}{2}-1}+b_{i-2} ai=Ci2i−2+Ci2i−1+bi−2
再考虑后手必胜态,因为发牌总方案数为 C i i 2 C_{i}^{\frac{i}{2}} Ci2i种,所以后手必胜态转移方程就是 b i = C i i 2 − a i − 1 b_i=C_{i}^{\frac{i}{2}}-a_i-1 bi=Ci2i−ai−1
代码实现
#include<bits/stdc++.h>
#define INF 2147483647LL
#define int long long
#define MAXN 300005
#define mod 998244353
#define PI 3.14
#define eps 1e-10
#define pa pair<int,int>
#define ms(a,x) memset(a,x,sizeof(a))
#define mc(ar1,ar2) memcpy(ar1,ar2,sizeof(ar2))
#define mkp(a,b) make_pair(a,b)
#define ls (p<<1)
#define rs (p<<1|1)
#define fn(i,st,ed) for(int i=st;i<=ed;++i)
#define fd(i,st,ed) for(int i=st;i>=ed;--i)
#define fg(i,x,head,e) for(int i=head[x];~i;i=e[i].nxt)
using namespace std;
//inline int read(){int =0,f=1;char c=getchar();while(!isdigit(c)){if(c=='-')f=-1;c=getchar();}while(isdigit(c)){x=x*10+c-'0';c=getchar();}return x*f;}
int a[MAXN],b[MAXN],d[MAXN],c[105][105];
void init(){fn(i,0,60){c[i][0]=c[i][i]=1;fn(j,1,i-1)c[i][j]=(c[i-1][j-1]+c[i-1][j])%mod;}a[2]=1,b[2]=0,d[2]=1;for(int i=4;i<=60;i+=2){d[i]=1;a[i]=c[i-2][(i>>1)-2]+c[i-2][(i-2)>>1]+b[i-2];a[i]%=mod;b[i]=mod+c[i][i>>1]-a[i]-1;b[i]%=mod;}
}
void solve(){int n;cin>>n;cout<<a[n]<<' '<<b[n]<<' '<<d[n]<<endl;
}
signed main(){ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);init();int T;cin>>T;while(T--){solve();}return 0;
}
H Maximum AND(1800)
原题链接:https://codeforces.com/problemset/problem/1721/D
题目大意
给定两个序列 a a a和 b b b,定义 f ( a , b ) f(a,b) f(a,b)为
1. 1. 1.对所有 1 ≤ i ≤ n 1\leq i \leq n 1≤i≤n,求 c i = a i ⊕ b i c_i=a_i \oplus b_i ci=ai⊕bi( ⊕ \oplus ⊕为按位异或)
2. 2. 2.对所有 1 ≤ i ≤ n 1\leq i \leq n 1≤i≤n,求与和,即 f ( a , b ) = c 1 & c 2 & c 3 . . . & c n f(a,b)=c_1\&c_2\&c_3...\&c_n f(a,b)=c1&c2&c3...&cn
对于 b i b_i bi的任意排列,求最大的 f ( a , b ) f(a,b) f(a,b)
题解
对于 f ( a , b ) f(a,b) f(a,b)的二进制分解,若想让某一位是 1 1 1,则需要所有 c i c_i ci的该位都是 1 1 1,即所有 a i a_i ai和 b i b_i bi在该位上都是不同的,而对于已经确定的某位是 1 1 1,需要确保后续交换顺序时该位数字和原来相同
所以对于已经确定的某位是 1 1 1,我们可以对所有 a i a_i ai和 b i b_i bi按照该位上的 ( 0 / 1 ) (0/1) (0/1)分开,递归进入子集,若子集合答案都能取到,则整体答案能取到
代码实现
#include<bits/stdc++.h>
#define INF 2147483647LL
#define int long long
#define MAXN 300005
#define mod 1000000007
#define PI 3.14
#define eps 1e-10
#define pa pair<int,int>
#define ms(a,x) memset(a,x,sizeof(a))
#define mc(ar1,ar2) memcpy(ar1,ar2,sizeof(ar2))
#define mkp(a,b) make_pair(a,b)
#define ls (p<<1)
#define rs (p<<1|1)
#define fn(i,st,ed) for(int i=st;i<=ed;++i)
#define fd(i,st,ed) for(int i=st;i>=ed;--i)
#define fg(i,x,head,e) for(int i=head[x];~i;i=e[i].nxt)
using namespace std;
inline int read(){int x=0,f=1;char c=getchar();while(!isdigit(c)){if(c=='-')f=-1;c=getchar();}while(isdigit(c)){x=x*10+c-'0';c=getchar();}return x*f;}
bool cmp(int x,int y){return x<y;}
bool cmp1(int x,int y){return x>y;}
int a[MAXN],b[MAXN];
void solve(){int n,ans=0;cin>>n;fn(i,1,n)cin>>a[i];fn(i,1,n)cin>>b[i];sort(a+1,a+n+1,cmp);sort(b+1,b+n+1,cmp1);fd(i,30,0){int tmp=n+1;fn(j,1,n)if((a[j]&(1<<i))==(b[j]&(1<<i))){tmp=j;break;}if(tmp>n)ans|=(1<<i);else {fn(j,1,n)a[j]|=(1<<i),b[j]|=(1<<i);sort(a+1,a+n+1,cmp);sort(b+1,b+n+1,cmp1);}}cout<<ans<<endl;
}
signed main(){ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);int T;cin>>T;while(T--){solve();}return 0;
}