Codeforces 16xx 合集
CF1616
A. Integer Diversity
直接用个map贪心,如果有相同的就反向即可。
B. Mirror in the String
这道题洛谷的翻译锅了,所以建议去看原题。
考虑这样一个字符串 baacc
,那么答案显然应该是 baaaab
。那么我们可以猜答案就是一段前缀不升的字符串。那证明是显然的,但是还漏考虑了一种情况。就是前两个字符相同的时候,直接取第一个字符即可。复杂度线性。
C. Representative Edges
\(a_1+a_2=a_1+a_2\),\(\frac{3}{2}(a_1+a_3)=a_1+a_2+a_3\),两式作差,可得 \(a_2-a_1=a_3-a_2\)。也就是说这是个等差序列(其实这不就是等差数列的求和公式吗)。那么直接枚举两个位置不动,剩下的位置的值也就可以算出来了。
D. Keep the Average High
直接暴力dp,用平衡树优化,复杂度线性对数。
E. Lexicographically Small Enough
直接找到第一个比这个位置小的,然后移过来,或者找到第一个和这个位置相同的之后变成子问题。
这个东西也可以用平衡树维护,复杂度也是线性对数。
F. Tricolor Triangles
这个好像没什么构造方法,直接列方程,由于三元环是 \(O(m^{1.5})\) 的,所以直接暴力高斯消元即可。然后由于这个矩阵贼稀疏,所以直接跑好像一点问题都没有。复杂度 \(O(m^{4.5})\)。其实复杂度没有这么高,只有 \(m^{4}\)。
可以bitset优化,但是vp的时候懒得仔细想了。
CF1628
A. Meximum Array
显然整个序列的mex最大,我们想要取到这个,找到第一个这个值的位置,显然mex递增。然后变成子任务。但其实不需要每次都算一遍全部的mex,因为每一段一定是一个后缀所以直接预处理好即可。
#include<bits/stdc++.h>
using namespace std;
const int maxn=2e5+5;
template<typename T>
void read(T &x){
T sgn=1;
char ch=getchar();
for(;!isdigit(ch);ch=getchar())
if(ch=='-')
sgn=-1;
for(x=0;isdigit(ch);ch=getchar())
x=x*10+ch-'0';
x*=sgn;
}
int n;
int a[maxn],mex[maxn],cnt[maxn];
vector<int>ans;
void MAIN(){
read(n);
for(int i=0;i<=n;i++)
cnt[i]=0;
for(int i=1;i<=n;i++)
read(a[i]);
mex[n+1]=0;
for(int i=n;i;i--){
cnt[a[i]]++;
mex[i]=mex[i+1];
while(cnt[mex[i]])
mex[i]++;
}
for(int i=0;i<=n;i++)
cnt[i]=0;
ans.clear();
for(int i=1,j;i<=n;i=j){
int now=0;
for(j=i;j<=n;j++){
cnt[a[j]]++;
while(cnt[now])
now++;
if(now==mex[i]){
ans.push_back(now);
j++;
break;
}
}
for(int k=i;k<j;k++)
cnt[a[k]]--;
}
printf("%d\n",(int)ans.size());
for(int i:ans)printf("%d ",i);
puts("");
}
int main(){
int T;
read(T);
while(T--)
MAIN();
return 0;
}
B. Peculiar Movie Preferences
一共只有几种方法:
-
本身就是一个回文串
-
2+2,3+3
-
2+3,3+2
手玩一下发现不会再有没被包含其他情况。
#include<bits/stdc++.h>
using namespace std;
const int maxn=2e5+5;
template<typename T>
void read(T &x){
T sgn=1;
char ch=getchar();
for(;!isdigit(ch);ch=getchar())
if(ch=='-')
sgn=-1;
for(x=0;isdigit(ch);ch=getchar())
x=x*10+ch-'0';
x*=sgn;
}
int n,len[maxn];
string s[maxn],t[maxn];
map<string,int>mp;
void MAIN(){
bool ans=false;
read(n);
for(int i=1;i<=n;i++){
cin>>s[i];
len[i]=s[i].length();
t[i]=s[i];
reverse(t[i].begin(),t[i].end());
if(s[i]==t[i])
ans=true;
}
mp.clear();
for(int i=1;i<=n;i++){
string now;
now="";
for(int j=0;j<2;j++)now+=t[i][j];
if(mp.find(now)!=mp.end())
ans=true;
if(mp.find(t[i])!=mp.end())
ans=true;
mp[s[i]]=1;
}
mp.clear();
for(int i=n;i;i--){
string now;
now="";
for(int j=0;j<2;j++)now+=s[i][j];
if(mp.find(now)!=mp.end())
ans=true;
if(mp.find(s[i])!=mp.end())
ans=true;
mp[t[i]]=1;
}
puts(ans?"YES":"NO");
}
int main(){
int T;
read(T);
while(T--)
MAIN();
return 0;
}
C. Grid Xor
发现直接暴力的构造就是对的。打出来之后也很可以看出规律。
#include<bits/stdc++.h>
using namespace std;
const int maxn=2e5+5;
template<typename T>
void read(T &x){
T sgn=1;
char ch=getchar();
for(;!isdigit(ch);ch=getchar())
if(ch=='-')
sgn=-1;
for(x=0;isdigit(ch);ch=getchar())
x=x*10+ch-'0';
x*=sgn;
}
int n,a[1005][1005];
bool vis[1005][1005];
void MAIN(){
read(n);
for(int i=0;i<=n+1;i++)
for(int j=0;j<=n+1;j++)
vis[i][j]=0;
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
read(a[i][j]);
int ans=0;
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
if(!vis[i-1][j]&&!vis[i][j-1]&&!vis[i+1][j]&&!vis[i][j+1]){
ans^=a[i][j];
vis[i-1][j]=vis[i][j-1]=vis[i+1][j]=vis[i][j+1]=1;
}
}
}
printf("%d\n",ans);
}
int main(){
int T;
read(T);
while(T--)
MAIN();
return 0;
}
D. Game on Sum (Hard Version)
这个 \(k\) 是来吓唬你的,显然最后给答案乘上 \(k\) 即可。然后考虑只能在 \(0,1\) 之间取数。设 \(f_{i,j}\) 代表还剩 \(i\) 次操作需要 \(j\) 次加操作。考虑转移,显然他会从 \(f_{i-1,j-1}+d,f_{i-1,j}-d\) 转移过来时有一种方法变成两者的平均数。于是他构成一个数表:
0
0 1
0 1/2 2
0 1/4 5/4 3
...
考虑右边的数对答案的贡献,容易发现向下一层就会/2,这个先不管。贡献次数即为路径条数,每次可以向下或右下走一步(除了第一步只能向下),所以共有 \(\binom{n-i-1}{m-i}\) 种走法。那么答案即为 \(\sum_{i=1}^{m}\binom{n-i-1}{m-i}2^{n-i}\)。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=1e6+5,mod=1e9+7;
int add(int a,int b){return a+b>=mod?a+b-mod:a+b;}
int dec(int a,int b){return a<b?a-b+mod:a-b;}
int mul(int a,int b){return 1ll*a*b%mod;}
int ksm(int a,int b=mod-2){int ret=1;for(;b;b>>=1,a=mul(a,a))if(b&1)ret=mul(ret,a);return ret;}
template<typename T>
void read(T &x){
T sgn=1;
char ch=getchar();
for(;!isdigit(ch);ch=getchar())
if(ch=='-')
sgn=-1;
for(x=0;isdigit(ch);ch=getchar())
x=x*10+ch-'0';
x*=sgn;
}
int n,m,k,fac[maxn],ifac[maxn];
int C(int a,int b){
if(a<0||b<0||a<b)return 0;
return mul(fac[a],mul(ifac[b],ifac[a-b]));
}
void MAIN(){
read(n);read(m);read(k);
fac[0]=ifac[0]=1;
for(int i=1;i<=n;i++)fac[i]=mul(fac[i-1],i);
ifac[n]=ksm(fac[n]);
for(int i=n-1;i;i--)ifac[i]=mul(ifac[i+1],i+1);
int ans=0;
if(m==n)ans=n;
else{
for(int i=1;i<=m;i++){
ans=add(ans,mul(i,mul(C(n-i-1,m-i),ksm(ksm(2,n-i)))));
}
}
printf("%d\n",mul(ans,k));
}
int main(){
int T;
read(T);
while(T--)
MAIN();
return 0;
}
E. Groceries in Meteor Town
E 才是最傻逼。
显然要求瓶颈路,那么建出kruscal重构树,然后答案即为 x 和每个白色点lca的lca的值。显然即为所有白色点lca和x的lca的值。也就是维护所有白色点的lca即可。lca显然可以合并,于是线段树直接做。
#include<bits/stdc++.h>
using namespace std;
const int maxn=6e5+5;
template<typename T>
void read(T &x){
T sgn=1;
char ch=getchar();
for(;!isdigit(ch);ch=getchar())
if(ch=='-')
sgn=-1;
for(x=0;isdigit(ch);ch=getchar())
x=x*10+ch-'0';
x*=sgn;
}
struct edge{
int u,v,w;
}e[maxn];
bool operator<(edge a,edge b){
return a.w<b.w;
}
int n,m,q,fa[maxn],cnt,id[maxn],val[maxn];
vector<int>vec[maxn];
int f[maxn][21],tr[maxn<<2],lca[maxn<<2],tag[maxn<<2],dep[maxn];
int Find(int x){
return fa[x]==x?x:fa[x]=Find(fa[x]);
}
int LCA(int u,int v){
if(dep[u]<dep[v])swap(u,v);
int d=dep[u]-dep[v];
for(int i=0;i<=20;i++)
if(d&(1<<i))
u=f[u][i];
if(u==v)
return u;
for(int i=20;~i;i--)
if(f[u][i]!=f[v][i]){
u=f[u][i];
v=f[v][i];
}
return f[u][0];
}
void dfs(int u){
for(int i=1;i<=20;i++)f[u][i]=f[f[u][i-1]][i-1];
for(int v:vec[u]){
f[v][0]=u;
dep[v]=dep[u]+1;
dfs(v);
}
}
void build(int p,int l,int r){
tr[p]=tag[p]=-1;
if(l==r)
return lca[p]=l,void();
int mid=(l+r)>>1;
build(p<<1,l,mid);
build(p<<1|1,mid+1,r);
lca[p]=LCA(lca[p<<1],lca[p<<1|1]);
}
void pusht(int p,int v){
if(v)tr[p]=-1;
else tr[p]=lca[p];
tag[p]=v;
}
void push_up(int p){
if(tr[p<<1]==-1)tr[p]=tr[p<<1|1];
else if(tr[p<<1|1]==-1)tr[p]=tr[p<<1];
else tr[p]=LCA(tr[p<<1],tr[p<<1|1]);
}
void push_down(int p){
if(tag[p]!=-1){
pusht(p<<1,tag[p]);
pusht(p<<1|1,tag[p]);
tag[p]=-1;
}
}
void modify(int p,int l,int r,int x,int y,int v){
if(l>y||r<x)
return;
if(x<=l&&r<=y)
return pusht(p,v),void();
int mid=(l+r)>>1;
push_down(p);
modify(p<<1,l,mid,x,y,v);
modify(p<<1|1,mid+1,r,x,y,v);
push_up(p);
}
void MAIN(){
read(n);read(q);
m=n-1;
for(int i=1;i<n;i++){
read(e[i].u);read(e[i].v);read(e[i].w);
}
sort(e+1,e+1+m);
cnt=n;
for(int i=1;i<=n;i++)id[i]=fa[i]=i;
for(int i=1;i<=m;i++){
int u=e[i].u,v=e[i].v;
int fu=Find(u),fv=Find(v);
if(fu==fv)continue;
val[++cnt]=e[i].w;
vec[cnt].push_back(id[fu]);
vec[cnt].push_back(id[fv]);
id[fu]=id[fv]=cnt;
fa[fu]=fv;
}
dfs(cnt);
build(1,1,n);
while(q--){
int op,l,r,x;
read(op);
if(op==1){
read(l);read(r);
modify(1,1,n,l,r,0);
}else if(op==2){
read(l);read(r);
modify(1,1,n,l,r,1);
}else{
read(x);
if(tr[1]==-1||tr[1]==x)puts("-1");
else printf("%d\n",val[LCA(tr[1],x)]);
}
}
}
int main(){
int T=1;
while(T--)
MAIN();
return 0;
}
A. GCD vs LCM
构造 \(1,n-3,1,1\) 即可。
B. Array Cloning Technique
贪心策略:复制一遍移到原来序列,再重复这个过程。
C. Tree Infection
现在所有非叶节点的儿子以及根处放一个,然后根据儿子大小从大到小排序贪心。剩下的部分模拟即可。
D. GCD Guess
考虑从低到高逐位确定,最多30位正好用完。\(\gcd(x+a,x+b)=\gcd(x+a,a-b)\),我们现在想知道第 \(i\) 位上是不是 \(1\),前 \(i-1\) 位已经确定为 \(x'\),那么我们只需要知道 \(\gcd(x-x'+2^i,2^{i+1})\) 即可。那么我们构造 \(a=3\times 2^{i}-x',b=2^{i}-x'\) 即可。验证可得这样符合条件。
E. MinimizOR
结论题,答案一定在最小的 \(k+1\) 个数里,所有数 \(<2^{k}\)。考虑归纳证明,假设所有数二进制下 \(<2^1\),也就是只有 \(0,1\),那么结论显然成立。考虑 \(k=x\) 时成立,推到 \(k=x+1\)。如果最高位全为 \(1\) 那么依然成立,否则一定选择两个最高位为 \(0\) 的,那么就是在最小的里面找,有可能最高位只有一个为 \(0\),那么就得多找一个数。
然后用线段树维护,时间复杂度 \(O(n\log n\log V)\)。
CF1677
A. Tokitsukaze and Strange Inequality
给定一个排列 \(p\),求有多少个四元组 \((a,b,c,d),a<b<c<d\) 满足 \(p_a<p_c\) 且 \(p_b>p_d\)。
\(1\le n\le 5000.\)
暴力枚举 \(a,c\),先枚举 \(c\) 再 枚举 \(a\)。然后扩展 \(a\) 的时候就可以求出 \(p_b>p_d\) 的个数,复杂度 \(O(n^2)\)。
B. Tokitsukaze and Meeting
现在 \(n\times m\) 名学生将按以下方式就坐
- 每一轮,所有学生坐到右侧的座位(最后一列的坐到下一行第一列),空出第一行第一列,当前学生在第一行第一列就坐。学生有属性 \(0/1\)。
对于每一时刻,有多少列或行满足,当前列/行存在属性为 \(1\) 的学生?
\(1\le n\times m\le 10^6.\)
一个傻逼做法。
显然行列做法不太一样我们分开来求。
列的话每次相当于一个循环位移,那么答案不会变小。直接维护即可。
行的话相当于 \(i-m\) 的答案加上第一行,那么只需要看一看 \([i-m+1,i]\) 这一段有没有 \(1\) 即可。
C. Tokitsukaze and Two Colorful Tapes
有两个颜色排列 \(a\) 和 \(b\),两个排列中相同颜色的位置可以填充 \(1\sim n\) 中的同一个数字且不可重复如果使得最后数字序列为:\(a_{i},b_{i}\)。要求最大化 \(\sum_{i=1}^{n}|numa_{i}-numb_{i}|\)
\(1\le n\le 10^5.\)
将相同颜色位置连起来,假设形成的每个环的长度为 \(L_i\),那么 \(m=\sum\lfloor\frac{L_i}{2}\rfloor\)。贪心地看,应该一大一笑论者放,那么平均一下,答案是 \(2m(n-m)\)。
D. Tokitsukaze and Permutations
有一个长度为 \(n\) 的数组 \(p\),将执行 \(k\) 次操作操作过程:对于 \(1\sim n\) 中,当\(p_{i}>p_{i+1}\),则交换 \(p_{i},p_{i+1}\) 经过 \(k\) 次操作之后,得到了一个新数组 \(a\)。
定义数组 \(v\) 表示在 \(1\sim i\) 中比 \(p_{i}\) 大的个数。现在给定 \(v\),但是有可能其中的值为 \(-1\),这表示它的值并不确定。求有多少种 \(p\) 满足在 \(k\) 次操作后得到的 \(v\) 和给定确定值一致。\(1\le n\le 10^6.\)
容易发现一次操作即为整体左移一位,然后 \(v_i=min(v_i-1,0)\),那么 \(p\) 和 \(v\) 就是双射。先判断 \(v\) 是否合法,然后将 \(v\) 还原。接下来对 \(v\) 计数,如果遇到 \(-1\),就是 \(i\) 种,否则遇到 \(0\) 就是 \(k+1\) 种,否则就是原先的值。
E. Tokitsukaze and Beautiful Subsegments
给定一个长度为 \(n\) 的排列 \(p\)。
定义一个区间 \([l,r]\) 是美丽的当且仅当该区间中存在两个 \(i,j\) 满足 \(l\leq i<j\leq r\),且有 \(p_ip_j=\max\limits_{k=l}^rp_k\)。
现有 \(q\) 组询问,每组询问给定区间 \([l_i,r_i]\),请你求出其有多少个子区间是美丽的。
\(1\leq n\leq2\times10^5,1\leq q\leq 10^6.\)
这个信息不是分治信息所以其实不好合并的,考虑找出所有 \((i,j,k)\),满足 \(a_ia_j=a_k\),其实只有 \(O(n\ln n)\) 组,那对于这个可以写成一个矩形。那么我们就是求矩形并,所以扫描线就好了 \(O(q\log n+n\log^2n)\)。
F. Tokitsukaze and Gems
我不知道这种东西出出来的意义是啥。