2025 省选做题记录(二)
Round #37 - 2024.12.12
A. [CF1997F] Chips on a Line
题目大意
给定
个棋子放在 上,每次操作可以:
- 把一个
上的棋子替换成两个分别在 上的棋子,或执行逆操作。 - 把
上的棋子移动到 上。 求有多少种放棋子的方案使得经过上述操作,能得到的棋子数量最小值是
。 数据范围:
。
思路分析
容易发现最优策略一定是把棋子都放在
一个位置
假设最终在
因此我们只关心最终
统计答案的时候判断
时间复杂度:
代码呈现
#include<bits/stdc++.h>
using namespace std;
const int MAXN=1005,MAXV=65005,MOD=998244353;
inline void add(int &x,const int &y) { x=(x+y>=MOD)?x+y-MOD:x+y; }
int f[MAXV][MAXN],fib[50];
int pcnt(int x) {
int s=0;
for(int k=30;k>=1;--k) if(x>=fib[k]) x-=fib[k],++s;
return s;
}
signed main() {
fib[1]=fib[2]=1;
for(int i=3;i<=30;++i) fib[i]=fib[i-1]+fib[i-2];
int n,q,m;
scanf("%d%d%d",&n,&q,&m);
int up=n*fib[q];
f[0][0]=1;
for(int i=q;i;--i) {
int z=fib[i];
for(int j=0;j+z<=up;++j) for(int k=0;k<n&&k*z<=j;++k) {
add(f[j+z][k+1],f[j][k]);
}
}
int ans=0;
for(int j=1;j<=up;++j) if(pcnt(j)==m) add(ans,f[j][n]);
printf("%d\n",ans);
return 0;
}
B. [CF2002F2] Court Blue
题目大意
给定两个变量
初始为 ,每次操作选择其中一个 ,要求始终保证: ,求可能的最大 。 数据范围:
。
思路分析
打表观察,可以发现当
然后直接 dp 即可,
时间复杂度
代码呈现
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int L=120;
bool f[L+5][L+5];
int bgcd(int x,int y) {
if(!x||!y||x==y) return x|y;
if(~x&1) return y&1?bgcd(x>>1,y):(bgcd(x>>1,y>>1)<<1);
return y&1?(x<y?bgcd((y-x)>>1,x):bgcd((x-y)>>1,y)):bgcd(x,y>>1);
}
void solve() {
int n,m,a,b;
scanf("%d%d%d%d",&n,&m,&a,&b);
memset(f,0,sizeof(f));
int u=max(1,n-L),v=max(1,m-L);
ll ans=0;
for(int i=0;i<=n-u;++i) for(int j=0;j<=m-v;++j) {
if(!i||!j) f[i][j]=1;
else if(bgcd(i+u,j+v)<=1) f[i][j]=f[i-1][j]|f[i][j-1];
if(f[i][j]) ans=max(ans,1ll*a*(i+u)+1ll*b*(j+v));
}
printf("%lld\n",ans);
}
signed main() {
int T; scanf("%d",&T);
while(T--) solve();
return 0;
}
C. [CF1995E2] Let Me Teach You a Lesson
题目大意
给定
,可以选择交换 ,最小化 。 数据范围:
。
思路分析
最小化极差难以维护,可以考虑枚举
有一个朴素的 dp:
转移为形如
那么我们从小到大枚举可能的
时间复杂度
代码呈现
#include<bits/stdc++.h>
using namespace std;
const int inf=2e9+5;
struct Mat {
array <array<int,2>,2> a;
Mat() { a={inf,inf,inf,inf}; }
inline friend Mat operator *(const Mat &u,const Mat &v) {
Mat w;
for(int i:{0,1}) for(int j:{0,1}) {
w.a[i][j]=min(max(u.a[i][0],v.a[0][j]),max(u.a[i][1],v.a[1][j]));
}
return w;
}
};
const int MAXN=2e5+5;
int n,a[MAXN][2];
Mat f[MAXN];
struct zKyGt1 {
Mat tr[1<<19];
int N;
void init() {
for(N=1;N<=n;N<<=1);
for(int i=1;i<(N<<1);++i) tr[i].a={0,inf,inf,0};
for(int i=1;i<=n;++i) tr[i+N]=f[i];
for(int i=N-1;i;--i) tr[i]=tr[i<<1]*tr[i<<1|1];
}
void upd(int x) {
for(tr[x+N]=f[x],x=(x+N)>>1;x;x>>=1) tr[x]=tr[x<<1]*tr[x<<1|1];
}
} T;
void solve() {
scanf("%d",&n);
for(int i=1;i<=n;++i) scanf("%d",&a[i][0]);
for(int i=1;i<=n;++i) scanf("%d",&a[i][1]);
vector <array<int,4>> op;
if(n%2==0) {
for(int i=1;i<=n;++i) {
if(i%2==0) { f[i].a={0,0,0,0}; continue; }
for(int x:{0,1}) for(int y:{0,1}) {
int l=a[i][x]+a[i+1][y],r=a[i][x^1]+a[i+1][y^1];
f[i].a[x][y]=max(l,r);
op.push_back({min(l,r),i,x,y});
}
}
} else {
a[n+1][0]=a[1][1],a[n+1][1]=a[1][0];
for(int i=1;i<=n;++i) {
for(int x:{0,1}) for(int y:{0,1}) {
if(i&1) f[i].a[x][y]=a[i][x]+a[i+1][y];
else f[i].a[x][y]=a[i][x^1]+a[i+1][y^1];
op.push_back({f[i].a[x][y],i,x,y});
}
}
}
T.init();
int ans=inf;
sort(op.begin(),op.end());
for(auto it:op) {
Mat &I=T.tr[1];
int v=min(I.a[0][0],I.a[1][1]);
if(v==inf) break;
ans=min(ans,v-it[0]);
f[it[1]].a[it[2]][it[3]]=inf;
T.upd(it[1]);
}
printf("%d\n",ans);
}
signed main() {
int _; scanf("%d",&_);
while(_--) solve();
return 0;
}
D. [CF2001E2] Deterministic Heap
题目大意
给定一棵高为
的满二叉树,进行 次操作,每次把一条到根的链上点权 ,容易发现形成大根堆。偶 执行两次 pop 操作,要求每次操作访问到的非叶子节点的左右儿子权值不相等,求方案数。
数据范围:
。
思路分析
称点权较大的儿子为重儿子,根的重儿子为重链。
能生成题目中的结构当且仅当
那么可以 dp:
然后考虑第二次 pop,此时如果
我们的限定条件变成
因此在 dp 状态中要记录重儿子的权值,
然后要讨论
- 如果
,那么相当于 的重子树支持 pop 两次,从 转移,而 的轻子树不需要 pop,从 转移。 - 否则左右子树都能 pop 一次,从
转移。
转移使用前缀和优化。
时间复杂度
代码呈现
#include<bits/stdc++.h>
using namespace std;
namespace FastMod {
typedef unsigned long long ull;
typedef __uint128_t uLL;
ull b,q,r; uLL m;
inline void init(const ull &B) { b=B,m=(uLL(1)<<64)/B; }
inline ull mod(const ull &a) {
r=a-((m*a)>>64)*b;
return r>=b?r-b:r;
}
}
#define o(x) FastMod::mod(x)
int n,k,MOD,f[505][505],g[505][505],s[505],h[505],u[505][505],t[505][505];
inline void add(int &x,const int &y) { x=(x+y>=MOD)?x+y-MOD:x+y; }
void solve() {
memset(f,0,sizeof(f));
memset(h,0,sizeof(h));
scanf("%d%d%d",&n,&k,&MOD),FastMod::init(MOD);
for(int i=1;i<=k;++i) for(int j=0;j<i&&i+j<=k;++j) f[i+j][i]+=2;
for(int i=1;i<=k;++i) for(int j=0;j<=i;++j) add(f[i][j],f[i-1][j]),u[i][j]=f[i][j];
for(int i=0;i<=k;++i) for(int j=0;i+j<=k;++j) ++h[i+j];
for(int i=1;i<=k;++i) add(h[i],h[i-1]);
for(int d=3;d<=n;++d) {
memset(t,0,sizeof(t));
for(int i=1;i<=k;++i) for(int j=0;j<=i;++j) {
t[i][j]=u[i][j],add(t[i][j],j?t[i][j-1]:0);
}
memset(g,0,sizeof(g));
for(int i=1;i<=k;++i) {
memset(s,0,sizeof(s));
for(int j=i;j>=0;--j) s[j]=f[i][j],add(s[j],s[j+1]);
for(int j=0;j<i&&i+j<=k;++j) {
g[i+j][i]=o(g[i+j][i]+2ll*h[j]*s[j+1]+2ll*t[i][j-1]*t[j][j]);
}
}
for(int i=1;i<=k;++i) for(int j=0;j<=i;++j) add(g[i][j],g[i-1][j]);
memcpy(f,g,sizeof(f));
memset(g,0,sizeof(g));
for(int i=1;i<=k;++i) for(int j=0;j<i&&i+j<=k;++j) {
g[i+j][i]=o(g[i+j][i]+2ll*t[i][i]*h[j]);
}
for(int i=1;i<=k;++i) for(int j=0;j<=i;++j) add(g[i][j],g[i-1][j]);
memcpy(u,g,sizeof(u));
memset(s,0,sizeof(s));
for(int i=0;i<=k;++i) for(int j=0;i+j<=k;++j) s[i+j]=o(s[i+j]+1ll*h[i]*h[j]);
for(int i=1;i<=k;++i) add(s[i],s[i-1]);
memcpy(h,s,sizeof(h));
}
int ans=0;
for(int i=0;i<=k;++i) add(ans,f[k][i]);
printf("%d\n",ans);
}
signed main() {
int T; scanf("%d",&T);
while(T--) solve();
return 0;
}
E. [CF2002G] Lattice Optimizing
题目大意
给定
网格,边带权,定义路径权值为经过的边权 ,求从左上走到右下的格路的最大权值。 数据范围:
。
思路分析
很显然无法优化维护
那么思路就是选取一条副对角线,每条路径恰好过其中一个点,爆搜出从起点 / 终点到对角线上每个点的路径对应的权值集合。
求答案是否
无法用 FWT 处理
那么设这条副对角线在
时间复杂度
代码呈现
#include<bits/stdc++.h>
#define ll long long
using namespace std;
struct HshT {
static const int MAXN=(1<<26)+5,P=3e7+1;
int sz,hd[P],to[MAXN];
ll w[MAXN];
void ins(ll x) {
int p=x%P;
for(int i=hd[p];i;i=to[i]) if(w[i]==x) return ;
w[++sz]=x,to[sz]=hd[p],hd[p]=sz;
}
bool qry(ll x) {
int p=x%P;
for(int i=hd[p];i;i=to[i]) if(w[i]==x) return true;
return false;
}
void init() {
for(int i=1;i<=sz;++i) hd[w[i]%P]=0,to[i]=w[i]=0;
sz=0;
}
} H;
int n,k,z,a[45][45],b[45][45];
const ll B=1ll<<40;
void dfs(int x,int y,ll s) {
if(x+y==k+2) {
while(H.qry((x*B)|(((1ll<<z)-1)&(~s)))) ++z;
return ;
}
if(x>1) dfs(x-1,y,s|1ll<<a[x-1][y]);
if(y>1) dfs(x,y-1,s|1ll<<b[x][y-1]);
}
void solve() {
scanf("%d",&n),H.init(),z=1;
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) for(int j=1;j<n;++j) scanf("%d",&b[i][j]);
k=2*(n-1)/3;
for(int p=0;p<(1<<k);++p) {
int i=1,j=1; ll s=0;
for(int o=0;o<k;++o) {
if(p>>o&1) s|=1ll<<a[i++][j];
else s|=1ll<<b[i][j++];
}
for(ll t=s;;t=(t-1)&s) {
H.ins((i*B)|t);
if(!t) break;
}
}
dfs(n,n,0);
printf("%d\n",z-1);
}
signed main() {
int T; scanf("%d",&T);
while(T--) solve();
return 0;
}
*F. [CF2002H] Counting 101
题目大意
给定一个序列
,一次操作可以将 替换成 。 定义一个序列的权值为:保证
的情况下能进行最多的操作次数。 给定
,对于每个 求有多少序列 权值为 。 数据范围:
。
思路分析
先刻画序列的权值,如果初始没有
这是显然的,我们不断操作序列最中间的元素,此时最终的元素是
那么如果有
观察相邻两个
- 一种是先把以
为中心的操作全部做完,剩下 的每一段元素用上述 的元素删剩下 个。 - 如果想剩下
个元素,那么我们要先在区间内操作几次,在不产生 的元素的情况下使得区间内的元素被两端 的元素的操作覆盖。
因此我们只关心每个
转移就是
-
首先要求
。 -
如果
,那么转移系数就是 。 -
否则考虑这一段区间内部,在值域
的情况下最少剩几个元素,很显然这就是值域 的子问题,设答案为 。如果
,系数是 ,否则系数是 。
然后解决原问题需要套一个 dp of dp,把
但
对于这种转移系数极差很小的情况,可以猜测
因此定义
即某种奇偶性的下表上全是
那么记录一个
此时状态仍然过大,进一步发现:
事实上可以证明
这还不够,我们发现同时记录
即最终内层 dp 的答案等价于这三个内层 dp 的答案相加减的结果。
感性理解就是用
那么每次转移后立刻分拆状态,转移的时候分讨
时间复杂度
代码呈现
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int n=130,m=30,MOD=1e9+7;
inline void add(int &x,const int &y) { x=(x+y>=MOD)?x+y-MOD:x+y; }
int pw[35][135],dp[35][135][135],f[135][135][135],g[135][135][135];
//dp[v,i,j]: val[1,v], len=i, max del=j
//f[i,j,l],g[i,j,r]: len=i, g0=j, [L0,R0] = [l,inf]/[0,r]
signed main() {
for(int i=0;i<=m;++i) for(int j=pw[i][0]=1;j<=n;++j) pw[i][j]=1ll*pw[i][j-1]*i%MOD;
dp[0][0][0]=1;
for(int v=1;v<=m;++v) {
memset(f,0,sizeof(f));
memset(g,0,sizeof(g));
g[0][0][0]=1;
for(int i=0;i<=n+1;++i) for(int j=0;j<=i;++j) {
for(int l=0;l<=i;++l) if(f[i][j][l]) add(f[i][j][l&1],MOD-f[i][j][l]); //split -> [0,l]-[0,inf]
for(int l=0;l<=i;++l) if(f[i][j][l]) {
const int w=f[i][j][l];
for(int k=0;i+k<=n;++k) {
const int z=1ll*w*pw[v-1][k]%MOD;
if(l>k) { //can't +0
int nr=k-((l+1)&1); //trans +1, (a+l-k)%2=1
if(nr>=0) add(g[i+k+1][j+2][nr],z); //max a = k-(l&1^1)
else add(g[i+k+1][j+3][0],z); //can't +1
} else add(g[i+k+1][j+1][k-l],z); //a+l<=k
}
}
for(int r=0;r<=i;++r) if(g[i][j][r]) {
const int w=g[i][j][r],l=r&1; //[l,r]
for(int k=0;i+k<=n;++k) {
const int z=1ll*w*pw[v-1][k]%MOD;
if(l>k) { //can't +0 (same)
int nr=k-((l+1)&1);
if(nr>=0) add(g[i+k+1][j+2][nr],z);
else add(g[i+k+1][j+3][0],z);
} else {
//d<=a+r,a+l<=k -> a in [d-r,k-l]
//split to [d-r,inf] + [0,k-l] - [0,inf]
add(g[i+k+1][j+1][k-l],z);
int *nw=f[i+k+1][j+1],*Z=dp[v-1][k];
for(int d=r+(k-l)%2;d<=k;d+=2) if(Z[d]) {
nw[d-r]=(nw[d-r]+1ll*w*Z[d])%MOD;
}
}
}
}
}
for(int i=1;i<=n+1;++i) for(int j=1;j<=i;++j) {
//0-th & i-th element is virtual, j=g[0]
for(int l=0;l<=i;++l) {
add(dp[v][i-1][j-1+(!l?0:(l&1?1:2))],f[i][j][l]);
}
for(int r=0;r<=i;++r) {
add(dp[v][i-1][j-1+(r&1)],g[i][j][r]);
}
}
}
int T; scanf("%d",&T);
for(int N,M;T--;) {
scanf("%d%d",&N,&M);
for(int K=0;K<=(N-1)/2;++K) printf("%d ",dp[M][N][N-2*K]); puts("");
}
return 0;
}
*G. [CF1994H] Fortnite
题目大意
交互器里有底数
和模数 ,每次可以询问字符串 ,得到哈希值 ,其中 ,在三次询问内求出 。 数据范围:
。
思路分析
定义
求出
然后我们要通过两次询问确定
那么假设
这就要求
转成左开右闭区间,就是
我们要保证
把
-
如果
,取 ,此时 ,且最终 上这一位为 。 -
否则向
借位,取 (借位前的),那么 上这一位的贡献为 。由于
至多被借一位,所以 ,因此取 时, ,所以此时 。
从而每一位的贡献都小于
那么
时间复杂度
代码呈现
#include<bits/stdc++.h>
#define ll long long
using namespace std;
int a[15],b[15],c[15],p;
int hsh(string s) {
cout<<"? "<<s<<endl;
int o; cin>>o; return o;
}
ll val(string s) {
reverse(s.begin(),s.end());
ll z=0;
for(char o:s) z=z*p+o-'a'+1;
return z;
}
void solve() {
p=hsh("aa")-1;
string X="zzzzzzzzzz";
int x=hsh(X);
for(int i=0,w=x;i<10;++i) a[i]=(i?26:25),b[i]=w%p,w/=p;
string s;
for(int i=0;i<10;++i) {
if(a[i]>b[i]) c[i]=a[i]-b[i];
else --a[i+1],c[i]=a[i];
s.push_back(c[i]-1+'a');
}
int y=hsh(s);
cout<<"! "<<p<<" "<<(val(X)-x)-(val(s)-y)<<endl;
}
signed main() {
int T; cin>>T;
while(T--) solve();
return 0;
}
Round #38 - 2024.12.16
A. [CF2006D] Iris and Adjacent Products
题目大意
定义一个数组是好的当且仅当可以重排该数组使得邻项乘积
。 给定
, 次询问某个子区间至少要修改多少个元素才是好的。 数据范围:
。
思路分析
先考虑什么样的数组是好的,对于所有
注意
用莫队或分块等方式维护区间中元素的出现次数。
每次修改一定是把
时间复杂度
代码呈现
#include<bits/stdc++.h>
using namespace std;
const int MAXN=1e5+5;
int n,k,q,a[MAXN];
int lp[325],rp[325],bl[MAXN];
int x[325][1005],y[325][1005],c[1005],d[1005];
void solve() {
cin>>n>>q>>k;
for(int i=1;i<=n;++i) cin>>a[i];
int B=sqrt(n),lim=sqrt(k);
for(int i=1;(i-1)*B+1<=n;++i) {
lp[i]=(i-1)*B+1,rp[i]=min(i*B,n);
memcpy(x[i],x[i-1],sizeof(x[i]));
memcpy(y[i],y[i-1],sizeof(y[i]));
for(int j=lp[i];j<=rp[i];++j) {
bl[j]=i;
if(a[j]<=lim) ++x[i][a[j]];
else ++y[i][k/a[j]];
}
}
for(int l,r;q--;) {
cin>>l>>r;
memset(c,0,sizeof(c));
memset(d,0,sizeof(d));
if(bl[l]==bl[r]) {
for(int j=l;j<=r;++j) {
if(a[j]<=lim) ++c[a[j]];
else ++d[k/a[j]];
}
} else {
for(int j=l;j<=rp[bl[l]];++j) {
if(a[j]<=lim) ++c[a[j]];
else ++d[k/a[j]];
}
for(int j=lp[bl[r]];j<=r;++j) {
if(a[j]<=lim) ++c[a[j]];
else ++d[k/a[j]];
}
for(int j=1;j<=lim;++j) {
c[j]+=x[bl[r]-1][j]-x[bl[l]][j];
d[j]+=y[bl[r]-1][j]-y[bl[l]][j];
}
}
int s=0;
for(int j=1;j<=lim;++j) {
c[j]+=c[j-1],d[j]+=d[j-1];
s=max(s,min((d[j]-c[j]+1)/2,(r-l+1)/2-c[j]));
}
cout<<s<<" ";
}
cout<<"\n";
}
signed main() {
ios::sync_with_stdio(false);
cin.tie(0),cout.tie(0);
int _; cin>>_;
while(_--) solve();
return 0;
}
B. [CF2004F] Make a Palindrome
题目大意
对于数组
,一次操作可以把 替换成 或进行逆操作。 定义数组权值和为:令数组回文的最小操作数,求
的每个子区间权值和。 数据范围:
。
思路分析
用这样的方式刻画数组
设
那么一次操作就是插入或删除一个隔板,而数组回文当且仅当隔板序列回文。
那么每个隔板都要在对面操作一次,除非其对面的位置恰好有隔板,即
容易发现两个和相等的不相邻子区间恰好对答案产生
注意相邻的两个子区间对答案产生
时间复杂度
代码呈现
#include<bits/stdc++.h>
#include<ext/pb_ds/hash_policy.hpp>
#include<ext/pb_ds/assoc_container.hpp>
using namespace std;
using namespace __gnu_pbds;
const int MAXN=2005;
int a[MAXN];
void solve() {
int n,ans=0;
cin>>n;
for(int i=1;i<=n;++i) cin>>a[i];
for(int i=1;i<=n;++i) ans+=(i-1)*(n-i+1);
gp_hash_table <int,int> cnt;
for(int i=1;i<=n;++i) {
for(int j=i,s=0;j<=n;++j) ans-=cnt[s+=a[j]];
for(int j=i,s=0;j>=1;--j) ++cnt[s+=a[j]];
for(int j=i-1,s=0;j>=1;--j) ++cnt[s+=a[j]];
}
cout<<ans<<"\n";
}
signed main() {
ios::sync_with_stdio(false);
int T; cin>>T;
while(T--) solve();
return 0;
}
C. [CF2003F] Turtle and Three Sequences
题目大意
给定
元素,每个元素有高度、颜色、权值,选出 个元素满足高度递增,颜色不同,且权值和最大。 数据范围
。
思路分析
这种选出不同颜色的元素的题可以考虑 random-coloring,即给每种颜色随机映射到
此时可以状压 dp,
对于最优解,可能被映射到
时间复杂度
代码呈现
#include<bits/stdc++.h>
using namespace std;
const int MAXN=3005,inf=1e9,T=400;
mt19937 rnd(time(0));
int n,m,a[MAXN],b[MAXN],c[MAXN],o[MAXN];
struct FenwickTree {
int tr[MAXN],s;
void init() { memset(tr,-0x3f,sizeof(tr)); }
void upd(int x,int v) { for(;x<=n;x+=x&-x) tr[x]=max(tr[x],v); }
int qry(int x) { for(s=-inf;x;x&=x-1) s=max(s,tr[x]); return s; }
} F[32];
signed main() {
scanf("%d%d",&n,&m);
for(int i=1;i<=n;++i) scanf("%d",&a[i]);
for(int i=1;i<=n;++i) scanf("%d",&b[i]);
for(int i=1;i<=n;++i) scanf("%d",&c[i]);
int ans=-1;
for(int _=0;_<T;++_) {
for(int s=0;s<(1<<m);++s) F[s].init();
F[0].upd(1,0);
for(int i=1;i<=n;++i) o[i]=rnd()%m;
for(int i=1;i<=n;++i) {
for(int s=0;s<(1<<m);++s) if(!(s>>o[b[i]]&1)){
int z=F[s].qry(a[i])+c[i];
F[s|1<<o[b[i]]].upd(a[i],z);
}
}
ans=max(ans,F[(1<<m)-1].qry(n));
}
printf("%d\n",ans);
return 0;
}
D. [CF2006E] Iris's Full Binary Tree
题目大意
定义一棵树的权值为最小的
使得该树是高度为 的满二叉树的导出子图。 给一棵
个点的树,对于 ,求 点的导出子图的权值。 数据范围:
。
思路分析
刻画一棵树的权值,显然这棵树过二叉树的根,且不能有
我们发现只要根节点是
显然根不可能是
取出树的中心,容易发现最优的
那么在树上不断加点,中心每次至多移动
每次中心移动都是对某个子树内或子树外的距离
此时答案就是线段树上的最小值。
时间复杂度
代码呈现
#include<bits/stdc++.h>
using namespace std;
const int MAXN=5e5+5,inf=1e9;
int n;
struct SegmentTree {
int tr[MAXN<<2],tg[MAXN<<2];
void psu(int p) { tr[p]=min(tr[p<<1],tr[p<<1|1]); }
void adt(int p,int k) { tg[p]+=k,tr[p]+=k; }
void psd(int p) { adt(p<<1,tg[p]),adt(p<<1|1,tg[p]),tg[p]=0; }
void init(int l=1,int r=n,int p=1) {
tr[p]=inf,tg[p]=0;
if(l==r) return ;
int mid=(l+r)>>1;
init(l,mid,p<<1),init(mid+1,r,p<<1|1);
}
void set(int u,int k,int l=1,int r=n,int p=1) {
if(l==r) return tr[p]=k,void();
int mid=(l+r)>>1; psd(p);
u<=mid?set(u,k,l,mid,p<<1):set(u,k,mid+1,r,p<<1|1);
psu(p);
}
void add(int ul,int ur,int k,int l=1,int r=n,int p=1) {
if(ul>ur) return ;
if(ul<=l&&r<=ur) return adt(p,k);
int mid=(l+r)>>1; psd(p);
if(ul<=mid) add(ul,ur,k,l,mid,p<<1);
if(mid<ur) add(ul,ur,k,mid+1,r,p<<1|1);
psu(p);
}
} T;
vector <int> G[MAXN];
int fa[MAXN],L[MAXN],R[MAXN],dep[MAXN],st[MAXN][20],dcnt;
void dfs(int u) {
dep[u]=dep[fa[u]]+1,L[u]=++dcnt,st[dcnt][0]=fa[u];
for(int v:G[u]) dfs(v);
R[u]=dcnt;
}
int bit(int x) { return 1<<x; }
int cmp(int x,int y) { return L[x]<L[y]?x:y; }
int LCA(int x,int y) {
if(x==y) return x;
int l=min(L[x],L[y])+1,r=max(L[x],L[y]),k=__lg(r-l+1);
return cmp(st[l][k],st[r-bit(k)+1][k]);
}
int dis(int x,int y) { return dep[x]+dep[y]-2*dep[LCA(x,y)]; }
int nxt(int x,int y) {
if(L[x]<=L[y]&&L[y]<=R[x]) {
return *--upper_bound(G[x].begin(),G[x].end(),y,[&](int i,int j){ return L[i]<L[j]; });
}
return fa[x];
}
int u,v,x,y,deg[MAXN];
void solve() {
cin>>n;
for(int i=1;i<=n;++i) G[i].clear(),deg[i]=0;
for(int i=2;i<=n;++i) cin>>fa[i],G[fa[i]].push_back(i);
dcnt=0,dfs(1);
for(int k=1;k<20;++k) for(int i=1;i+bit(k)-1<=n;++i) {
st[i][k]=cmp(st[i][k-1],st[i+bit(k-1)][k-1]);
}
u=v=x=y=1,T.init(),T.set(1,0),cout<<"1 ";
for(int p=2;p<=n;++p) {
++deg[fa[p]],++deg[p];
T.set(L[p],min(dis(p,x),dis(p,y)));
if(deg[fa[p]]>3) {
for(int i=p;i<=n;++i) cout<<"-1 ";
cout<<"\n";
return ;
}
if(deg[fa[p]]==3) T.set(L[fa[p]],inf);
if(dis(u,p)<dis(v,p)) swap(u,v),swap(x,y);
if(dis(u,p)>dis(u,v)) {
if(x!=y) {
if(fa[x]==y) T.add(L[x],R[x],1);
else T.add(1,L[y]-1,1),T.add(R[y]+1,n,1);
x=y;
} else {
y=nxt(y,p);
if(fa[y]==x) T.add(L[y],R[y],-1);
else T.add(1,L[x]-1,-1),T.add(R[x]+1,n,-1);
}
v=p;
}
cout<<T.tr[1]+(dis(u,v)+1)/2+1<<" ";
}
cout<<"\n";
}
signed main() {
ios::sync_with_stdio(false);
int _; cin>>_;
while(_--) solve();
return 0;
}
*E. [CF1870G] MEXanization
题目大意
定义一个多重集
的权值为:每次选取一个 的子集 ,删除 并加入 ,所能得到的最大元素。 给定
,求出 的每个前缀的权值。 数据范围:
。
思路分析
发现当
这要求我们有
然后考虑
然后重复类似的过程,最后判断
形式化的来说,记
初始
注意到如果
可以线段树优化找元素的过程,做到
但实际上能做到更优,考虑在线段树上一边 dfs 一边处理,如果
此时第
时间复杂度
代码呈现
#include<bits/stdc++.h>
using namespace std;
const int MAXN=2e5+5,inf=2e9;
int n,a[MAXN];
struct SegmentTree {
int su[MAXN<<2],mn[MAXN<<2];
void add(int u,int l=0,int r=n,int p=1) {
++su[p];
if(l==r) return ++mn[p],void();
int mid=(l+r)>>1;
u<=mid?add(u,l,mid,p<<1):add(u,mid+1,r,p<<1|1);
mn[p]=min(mn[p<<1],mn[p<<1|1]);
}
void qry(int ul,int ur,int &k,int &z,int s,int l=0,int r=n,int p=1) {
if(ul<=l&&r<=ur) {
if(k>s/r) return k=inf,void();
if(mn[p]>=k) return z+=su[p]-(r-l+1)*k,void();
if(l==r) {
if(k<mn[p]) z+=mn[p]-k;
else k+=k-mn[p];
return ;
}
}
int mid=(l+r)>>1;
if(mid<ur) qry(ul,ur,k,z,s,mid+1,r,p<<1|1);
if(ul<=mid) qry(ul,ur,k,z,s,l,mid,p<<1);
}
int qs(int ul,int ur,int l=0,int r=n,int p=1) {
if(ul<=l&&r<=ur) return su[p];
int mid=(l+r)>>1,s=0;
if(ul<=mid) s+=qs(ul,ur,l,mid,p<<1);
if(mid<ur) s+=qs(ul,ur,mid+1,r,p<<1|1);
return s;
}
} T;
void solve() {
cin>>n;
for(int i=1;i<=n;++i) cin>>a[i];
int x=0;
for(int i=1;i<=n;++i) {
T.add(min(a[i],n));
for(;x<i;++x) {
int k=1,z=T.qs(x+1,n)+T.qs(0,0);
if(x) T.qry(1,x,k,z,i);
if(z<k) break;
}
cout<<(i==1?max(a[i],x):x)<<" ";
}
cout<<"\n";
for(int i=1;i<=(n+1)*4;++i) T.su[i]=T.mn[i]=0;
}
signed main() {
ios::sync_with_stdio(false);
int _; cin>>_;
while(_--) solve();
return 0;
}
*F. [CF1896H2] Cyclic Hamming
题目大意
给定
,定义两个长度为 的 01 串 是好的当且仅当:
分别含有 个 。 - 对于每个
的循环移位 , , 表示海明距离。 给定含有未确定字符的字符串
,求所有填充方式中有多少使得 是好的。 数据范围:
。
思路分析
记
首先
用卷积刻画循环移位的海明距离,首先由于
然后构造两个多项式
因此题目限制就是
用减法代替多项式取模,即
注意到右式有公因式:
先要求
相当于要求带入
因此合法的充要条件就是所有
刻画这个条件,对
也就是说,把
用 Trie 树分类,把
带入
因此
那么对于
暴力转移复杂度
用观察优化,设
那么枚举
如果用 NTT 优化左右子树的卷积,复杂度可以做到
时间复杂度
代码呈现
#include<bits/stdc++.h>
#define pc __builtin_popcount
#define ll long long
using namespace std;
const int MOD=998244353,MAXN=(1<<13)+5;
vector<vector<int>> f[14][MAXN];
//f[i,s,u,j]: dep = i, ok dig = s, node u, size = j*2^|s|
int n,k,rv[MAXN];
inline void add(int &x,const ll &y) { x=(x+y)%MOD; }
void DP(char *str,int *dp) {
for(int i=1;i<n;++i) rv[i]=(rv[i>>1]>>1)|(i&1?n>>1:0);
for(int i=0;i<n;++i) if(rv[i]>i) swap(str[i],str[rv[i]]);
for(int i=0;i<=k+1;++i) for(int s=0;s<(1<<i);++s) {
int U=1<<(k+1-i),J=1<<(i-pc(s));
f[i][s]=vector<vector<int>>(U,vector<int>(J+1,0));
}
for(int i=0;i<n;++i) {
f[0][0][i][0]=(str[i]!='1');
f[0][0][i][1]=(str[i]!='0');
}
for(int i=1;i<=k+1;++i) for(int s=0;s<(1<<(i-1));++s) {
int U=1<<(k+1-i),J=1<<(i-pc(s)-1);
for(int u=0;u<U;++u) {
auto &fl=f[i-1][s][u<<1],&fr=f[i-1][s][u<<1|1];
for(int x=0;x<=J;++x) {
add(f[i][s|(1<<(i-1))][u][x],1ll*fl[x]*fr[x]);
}
auto &fi=f[i][s][u];
for(int x=0;x<=J;++x) if(fl[x]) for(int y=0;y<=J;++y) if(fr[y]) {
add(fi[x+y],1ll*fl[x]*fr[y]);
}
}
}
for(int s=0;s<n-1;++s) dp[s]=f[k+1][s][0][1<<(k-pc(s))];
}
char S[MAXN],T[MAXN];
int fs[MAXN],ft[MAXN];
signed main() {
scanf("%d%s%s",&k,S,T),n=1<<(k+1);
reverse(T,T+n);
DP(S,fs),DP(T,ft);
for(int i=0;i<=k;++i) for(int s=0;s<n;++s) if(!(s>>i&1)) fs[s]=(fs[s]+MOD-fs[s|1<<i])%MOD;
int ans=0;
for(int s=0;s<n;++s) add(ans,1ll*fs[s]*ft[(n-1)^s]);
printf("%d\n",ans);
return 0;
}
Round #39 - 2024.12.18
A. [CF2009G3] Yunli's Subarray Queries
题目大意
给定
,定义序列权值为至少要修改几个位置才能使得其中存在一个长度 的公差为 的等差数列。 给定
, 次询问 $$求这个值的和。 数据范围:
。
思路分析
先计算每个长度为
记
因此答案就是
时间复杂度
代码呈现
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=2e5+5;
int n,m,q,a[MAXN];
struct SegmentTree {
int len[MAXN<<2];
ll sum[MAXN<<2],sumh[MAXN<<2],ad[MAXN<<2],ti[MAXN<<2],sd[MAXN<<2];
void trs(int p) {
sumh[p]+=sum[p],sd[p]+=ad[p],++ti[p];
}
void adt(int p,ll k) {
sum[p]+=k*len[p],ad[p]+=k;
}
void psd(int p) {
for(int c:{p<<1,p<<1|1}) {
sumh[c]+=sum[c]*ti[p]+len[c]*sd[p];
sd[c]+=ad[c]*ti[p]+sd[p];
sum[c]+=ad[p]*len[c],ad[c]+=ad[p],ti[c]+=ti[p];
}
ad[p]=ti[p]=sd[p]=0;
}
void psu(int p) { sum[p]=sum[p<<1]+sum[p<<1|1],sumh[p]=sumh[p<<1]+sumh[p<<1|1]; }
void init(int l=1,int r=n,int p=1) {
len[p]=r-l+1,sum[p]=sumh[p]=ad[p]=ti[p]=sd[p]=0;
if(l==r) return ;
int mid=(l+r)>>1; init(l,mid,p<<1),init(mid+1,r,p<<1|1);
}
void add(int ul,int ur,ll k,int l=1,int r=n,int p=1) {
if(ul<=l&&r<=ur) return adt(p,k);
int mid=(l+r)>>1; psd(p);
if(ul<=mid) add(ul,ur,k,l,mid,p<<1);
if(mid<ur) add(ul,ur,k,mid+1,r,p<<1|1);
psu(p);
}
ll qry(int ul,int ur,int l=1,int r=n,int p=1) {
if(ul<=l&&r<=ur) return sumh[p];
int mid=(l+r)>>1; ll ans=0; psd(p);
if(ul<=mid) ans+=qry(ul,ur,l,mid,p<<1);
if(mid<ur) ans+=qry(ul,ur,mid+1,r,p<<1|1);
return ans;
}
} T;
int c[MAXN*2],w[MAXN],mx,v[MAXN];
void add(int x) {
--w[c[x]],++w[++c[x]];
if(w[mx+1]) ++mx;
}
void del(int x) {
--w[c[x]],++w[--c[x]];
if(!w[mx]) --mx;
}
int stk[MAXN];
ll ans[MAXN];
vector <array<int,2>> Q[MAXN];
void solve() {
cin>>n>>m>>q,mx=0,w[0]=n;
for(int i=1;i<=n;++i) cin>>a[i],a[i]=a[i]-i+n;
for(int i=1;i<=m;++i) add(a[i]);
v[1]=m-mx;
for(int i=2;i<=n-m+1;++i) {
del(a[i-1]),add(a[i+m-1]),v[i]=m-mx;
}
for(int i=1,l,r;i<=q;++i) {
cin>>l>>r,Q[r-m+1].push_back({l,i});
}
T.init();
for(int i=1,tp=0;i<=n-m+1;++i) {
for(;tp&&v[stk[tp]]>=v[i];--tp) T.add(stk[tp-1]+1,stk[tp],-v[stk[tp]]);
T.add(stk[tp]+1,i,v[i]),stk[++tp]=i;
T.trs(1);
for(auto o:Q[i]) ans[o[1]]=T.qry(o[0],i);
}
for(int i=1;i<=q;++i) cout<<ans[i]<<"\n";
for(int i=0;i<=n*2;++i) c[i]=0;
for(int i=0;i<=n;++i) w[i]=0,Q[i].clear();
}
signed main() {
ios::sync_with_stdio(false);
cin.tie(0),cout.tie(0);
int _; cin>>_;
while(_--) solve();
return 0;
}
B. [CF2013F2] Digital Village
题目大意
给定
个点的树,先手从 出发,后手从 出发,两个人轮流在树上游走,不能重复经过点,对于 上的每个 ,求谁有必胜策略。 数据范围:
。
思路分析
假设
设
假设先手在
假设后手在
暴力枚举每个
定义
我们要找
注意到
找到
- 如果
,那么 的点都不可能合法。 - 否则
的点都不可能合法,我们只要判断 中有没有合法点,很显然最容易合法的就是 ,只需要判断 是否合法即可。
因此我们只需要判定
不断这样的递归,我们会需要判定的
考虑
由于
在 dfs 的时候动态维护栈中节点的
可以用 st 表,
此时我们能得到后手从每个
时间复杂度
代码呈现
#include<bits/stdc++.h>
using namespace std;
const int MAXN=2e5+5;
inline int bit(int x) { return 1<<x; }
struct ST {
int v[MAXN],f[MAXN][18];
int cmp(int x,int y) { return v[x]>v[y]?x:y; }
void upd(int x,int z) {
v[x]=z,f[x][0]=x;
for(int k=1;bit(k)<=x;++k) f[x][k]=cmp(f[x][k-1],f[x-bit(k-1)][k-1]);
}
int qry(int l,int r) {
int k=__lg(r-l+1);
return cmp(f[l+bit(k)-1][k],f[r][k]);
}
} A,B;
void upd(int d,int w) { A.upd(d,w+d-1),B.upd(d,w-d); }
bool sol(int n) {
int mid=(n+1)>>1;
for(int l=A.qry(1,mid),r=B.qry(mid+1,n);;) {
if(l-1<=n-r) {
if(A.v[l]>B.v[B.qry(l+1,n-l+1)]+n) return 1;
if(l>=mid) return 0;
l=A.qry(l+1,mid);
} else {
if(B.v[r]+n>=A.v[A.qry(n-r+2,r-1)]) return 0;
if(r<=mid+1) return 1;
r=B.qry(mid+1,r-1);
}
}
}
vector <int> G[MAXN];
int n,d[MAXN],f[MAXN],fa[MAXN];
bool ans[MAXN];
void dfs1(int u,int fz) {
d[u]=d[fz]+1,f[u]=0,fa[u]=fz;
for(int v:G[u]) if(v^fz) dfs1(v,u),f[u]=max(f[u],f[v]+1);
}
void dfs2(int u) {
int mx=0,sx=0;
for(int v:G[u]) if(v^fa[u]) {
if(f[v]+1>mx) sx=mx,mx=f[v]+1;
else sx=max(sx,f[v]+1);
}
if(u>1) upd(d[u],mx),ans[u]=sol(d[u]);
for(int v:G[u]) if(v^fa[u]) upd(d[u],f[v]+1==mx?sx:mx),dfs2(v);
}
void prt(int u) { cout<<(ans[u]?"Alice":"Bob")<<"\n"; }
void out(int u,int v) {
if(u==v) return prt(u);
if(d[u]>d[v]) prt(u),out(fa[u],v);
else out(u,fa[v]),prt(v);
}
void solve() {
cin>>n;
for(int i=1;i<=n;++i) G[i].clear();
for(int i=1,u,v;i<n;++i) cin>>u>>v,G[u].push_back(v),G[v].push_back(u);
dfs1(1,0),dfs2(1);
int s,t;
cin>>s>>t,out(s,t);
}
signed main() {
ios::sync_with_stdio(false);
cin.tie(0),cout.tie(0);
int _; cin>>_;
while(_--) solve();
return 0;
}
C. [CF2020F] Count Leaves
题目大意
给定
,构造一棵深度为 的有根树,根节点的标号为 ,标号为 的节点有 个儿子,标号为分别为 的每个因数。 设
为这棵树的叶子个数,求 。 数据范围:
。
思路分析
先刻画
容易发现
因此
从小大大爆搜每个质因数,枚举
而最大质因子出现次数
时间复杂度
代码呈现
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=2e5+5,MOD=1e9+7,MAXV=3.2e6+5;
ll fac[MAXV],ifac[MAXV];
ll ksm(ll a,ll b=MOD-2) { ll s=1; for(;b;a=a*a%MOD,b>>=1) if(b&1) s=s*a%MOD; return s; }
ll C(int x,int y) {
if(x<0||y<0||y>x) return 0;
return fac[x]*ifac[y]%MOD*ifac[x-y]%MOD;
}
int n,K,D,B,val[MAXN];
bool isc[MAXN];
int p[MAXN],tot,m,idx1[MAXN],idx2[MAXN],g[MAXN];
inline int idx(int v) {
return (v<=B)?idx1[v]:idx2[n/v];
}
ll f[32],ans;
void dfs(int i,int N,ll dp) {
if(g[idx(N)]>i) ans=(ans+dp*f[1]%MOD*(g[idx(N)]-i))%MOD;
for(int j=i+1;j<=tot&&p[j]<=N/p[j];++j) {
for(int c=1,M=N/p[j];M>=p[j];++c,M/=p[j]) {
ans=(ans+dp*f[c+1])%MOD;
dfs(j,M,dp*f[c]%MOD);
}
}
}
void solve() {
scanf("%d%d%d",&n,&K,&D),B=sqrt(n),tot=m=0;
for(int i=2;i<=B;++i) {
if(!isc[i]) p[++tot]=i;
for(int j=1;j<=tot&&i*p[j]<=B;++j) {
isc[i*p[j]]=true;
if(i%p[j]==0) break;
}
}
for(ll l=1,r;l<=n;l=r+1) {
r=n/(n/l),val[++m]=n/l;
if(val[m]<=B) idx1[val[m]]=m;
else idx2[n/val[m]]=m;
g[m]=val[m]-1;
}
for(int k=1;k<=tot;++k) {
for(int i=1;i<=m&&1ll*p[k]*p[k]<=val[i];++i) {
g[i]-=g[idx(val[i]/p[k])]-(k-1);
}
}
for(int i=1;i<=30;++i) f[i]=C(K*i+D,D);
ans=1,dfs(0,n,1);
printf("%lld\n",ans);
for(int i=1;i<=m;++i) val[i]=g[i]=0;
for(int i=1;i<=B;++i) idx1[i]=idx2[i]=0,isc[i]=false;
}
signed main() {
for(int i=fac[0]=1;i<MAXV;++i) fac[i]=fac[i-1]*i%MOD;
ifac[MAXV-1]=ksm(fac[MAXV-1]);
for(int i=MAXV-1;i;--i) ifac[i-1]=ifac[i]*i%MOD;
int _; scanf("%d",&_);
while(_--) solve();
return 0;
}
*D. [CF2018E2] Complex Segments
题目大意
给定
个区间,选出若干个区间,使得其可以分割成若干个大小相同的子集,且两个区间相交当且仅当他们属于同一个子集,最大化选出区间个数。 数据范围:
。
思路分析
一个子集中的线段两两有交,相当于有一个公共点。
很自然的想法是枚举每个子集的大小
一种求
此时我们得到了
注意到
此时考虑第
那么总的查询次数就是
直接计算可以做到
需要优化计算
首先我们可以离散化,使得所有区间的左右端点互不重合,那么后缀最大值每次变化量为
给
如果
时间复杂度
代码呈现
#include<bits/stdc++.h>
using namespace std;
const int MAXN=6e5+5;
array<int,2> a[MAXN];
int n,dsu[MAXN],ans;
int find(int x) { return dsu[x]^x?dsu[x]=find(dsu[x]):x; }
int f(int k) {
iota(dsu,dsu+2*n+1,0);
int s=0,lim=0,lst=0,cnt=0;
for(int i=1;i<=n;++i) {
int l=a[i][0],r=a[i][1];
if(l<=lim) continue;
for(int u=lst+1;u<r;++u) dsu[u]=lst;
int p=find(l); lst=r;
if(p<=lim) ++cnt;
else dsu[p]=p-1;
if(cnt>=k) cnt=0,lim=r,++s;
}
ans=max(ans,s*k);
return s;
}
void cdq(int l,int r,int lo,int hi) {
if(l>r||lo==hi) return ;
int mid=(l+r)>>1,z=f(mid);
cdq(l,mid-1,lo,z),cdq(mid+1,r,z,hi);
}
void solve() {
cin>>n;
vector <array<int,2>> st;
for(int i=1;i<=n;++i) cin>>a[i][0],st.push_back({a[i][0],-i});
for(int i=1;i<=n;++i) cin>>a[i][1],st.push_back({a[i][1],i});
sort(st.begin(),st.end());
for(int i=0;i<2*n;++i) {
if(st[i][1]<0) a[-st[i][1]][0]=i+1;
else a[st[i][1]][1]=i+1;
}
sort(a+1,a+n+1,[&](auto x,auto y){ return x[1]<y[1]; });
ans=0,cdq(2,n-1,f(1),f(n));
cout<<ans<<"\n";
}
signed main() {
ios::sync_with_stdio(false);
int _; cin>>_;
while(_--) solve();
return 0;
}
*E. [CF2013F2] Game in Tree
题目大意
给定
个点的树,先手从 出发,后手从 出发,两个人轮流在树上游走,不能重复经过点,对于 上的每个 ,求谁有必胜策略。 数据范围:
。
思路分析
假设
设
假设先手在
假设后手在
暴力枚举每个
定义
我们要找
注意到
找到
- 如果
,那么 的点都不可能合法。 - 否则
的点都不可能合法,我们只要判断 中有没有合法点,很显然最容易合法的就是 ,只需要判断 是否合法即可。
因此我们只需要判定
不断这样的递归,我们会需要判定的
考虑
由于
在 dfs 的时候动态维护栈中节点的
可以用 st 表,
此时我们能得到后手从每个
时间复杂度
代码呈现
#include<bits/stdc++.h>
using namespace std;
const int MAXN=2e5+5;
inline int bit(int x) { return 1<<x; }
struct ST {
int v[MAXN],f[MAXN][18];
int cmp(int x,int y) { return v[x]>v[y]?x:y; }
void upd(int x,int z) {
v[x]=z,f[x][0]=x;
for(int k=1;bit(k)<=x;++k) f[x][k]=cmp(f[x][k-1],f[x-bit(k-1)][k-1]);
}
int qry(int l,int r) {
int k=__lg(r-l+1);
return cmp(f[l+bit(k)-1][k],f[r][k]);
}
} A,B;
void upd(int d,int w) { A.upd(d,w+d-1),B.upd(d,w-d); }
bool sol(int n) {
int mid=(n+1)>>1;
for(int l=A.qry(1,mid),r=B.qry(mid+1,n);;) {
if(l-1<=n-r) {
if(A.v[l]>B.v[B.qry(l+1,n-l+1)]+n) return 1;
if(l>=mid) return 0;
l=A.qry(l+1,mid);
} else {
if(B.v[r]+n>=A.v[A.qry(n-r+2,r-1)]) return 0;
if(r<=mid+1) return 1;
r=B.qry(mid+1,r-1);
}
}
}
vector <int> G[MAXN];
int n,d[MAXN],f[MAXN],fa[MAXN];
bool ans[MAXN];
void dfs1(int u,int fz) {
d[u]=d[fz]+1,f[u]=0,fa[u]=fz;
for(int v:G[u]) if(v^fz) dfs1(v,u),f[u]=max(f[u],f[v]+1);
}
void dfs2(int u) {
int mx=0,sx=0;
for(int v:G[u]) if(v^fa[u]) {
if(f[v]+1>mx) sx=mx,mx=f[v]+1;
else sx=max(sx,f[v]+1);
}
if(u>1) upd(d[u],mx),ans[u]=sol(d[u]);
for(int v:G[u]) if(v^fa[u]) upd(d[u],f[v]+1==mx?sx:mx),dfs2(v);
}
void prt(int u) { cout<<(ans[u]?"Alice":"Bob")<<"\n"; }
void out(int u,int v) {
if(u==v) return prt(u);
if(d[u]>d[v]) prt(u),out(fa[u],v);
else out(u,fa[v]),prt(v);
}
void solve() {
cin>>n;
for(int i=1;i<=n;++i) G[i].clear();
for(int i=1,u,v;i<n;++i) cin>>u>>v,G[u].push_back(v),G[v].push_back(u);
dfs1(1,0),dfs2(1);
int s,t;
cin>>s>>t,out(s,t);
}
signed main() {
ios::sync_with_stdio(false);
cin.tie(0),cout.tie(0);
int _; cin>>_;
while(_--) solve();
return 0;
}
Round #40 - 2024.12.19
A. [CF2032F] Peanuts
题目大意
给定
,先手先将 分成若干个连续段,然后两个人从左到右依次在每个连续段内部做 Nim 游戏,求有多少分段策略使得先手必胜。 数据范围:
。
思路分析
考虑如何判定一个局面是先手必胜还是后手必胜,从最后一个连续段开始,先手肯定要赢下这个游戏。
那么这个游戏如果先手必胜,则先手要输掉前面那个游戏,使得他能在这个游戏中成为先手,否则先手要赢下前面那个游戏。
因此从后往前就能推断出想要获胜的人要在每个游戏中赢还是输。
于是可以 dp,
转移系数就是
先手有必胜策略就是 Nim 博弈,即异或和
先手有必败策略则是 Anti-Nim 博弈,判定条件是
因此暴力 dp 可以做到
优化转移只需要对异或前缀和开桶,特殊讨论
时间复杂度
代码呈现
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=1e6+5,MOD=998244353;
int n,a[MAXN],s[MAXN];
ll f[MAXN][2],g[MAXN][2];
//f[i][0/1]: fill [i,n], lose/win game at i-1
void solve() {
cin>>n;
for(int i=1;i<=n;++i) cin>>a[i];
vector <int> vals{0};
for(int i=n;i>=1;--i) vals.push_back(s[i]=s[i+1]^a[i]);
sort(vals.begin(),vals.end()),vals.erase(unique(vals.begin(),vals.end()),vals.end());
for(int i=1;i<=n+1;++i) s[i]=lower_bound(vals.begin(),vals.end(),s[i])-vals.begin()+1;
g[s[n+1]][1]=g[0][1]=f[n+1][1]=1;
int h[2]={0,0};
for(int i=n,j=n+1;i>=1;--i) {
if(a[i]>1) {
h[0]=h[1]=0;
for(;j>i;--j) {
g[s[j]][0]=(g[s[j]][0]+f[j][0])%MOD;
g[0][0]=(g[0][0]+f[j][0])%MOD;
}
}
f[i][1]=(f[i][1]+g[s[i]][1])%MOD;
f[i][0]=(f[i][0]+g[0][1]+MOD-g[s[i]][1])%MOD;
f[i][1]=(f[i][1]+g[s[i]][0])%MOD;
f[i][0]=(f[i][0]+g[0][0]+MOD-g[s[i]][0])%MOD;
f[i][1]=(f[i][1]+h[(i+1)&1])%MOD;
f[i][0]=(f[i][0]+h[i&1])%MOD;
g[s[i]][1]=(g[s[i]][1]+f[i][1])%MOD;
g[0][1]=(g[0][1]+f[i][1])%MOD;
h[i&1]=(h[i&1]+f[i][0])%MOD;
}
cout<<f[1][0]<<"\n";
for(int i=0;i<=n+1;++i) {
a[i]=s[i]=f[i][0]=f[i][1]=g[i][0]=g[i][1]=0;
}
}
signed main() {
ios::sync_with_stdio(false);
int T; cin>>T;
while(T--) solve();
return 0;
}
B. [CF2022D2] Asesino
题目大意
给定
个人,每个人是骑士或骗子,骑士总说真话,骗子总说假话,但现在有一个骗子进行了伪装,使得其他人都认为他是骑士。 你可以询问第
个人是否认为第 个人是骑士,在最少次数内确定伪装的骗子。 数据范围:
。
思路分析
询问
如果
否则需要
因此先询问一个三元环,即可变成
此时在
可以证明不能更小,否则我们可以把欺骗者安排到没有入度的点或没有出度的点上。
时间复杂度
代码呈现
#include<bits/stdc++.h>
using namespace std;
bool qry(int x,int y) {
cout<<"? "<<x<<" "<<y<<endl;
int z; cin>>z; return z^1;
}
void solve() {
int n;
cin>>n;
if(n&1) {
if(n==3) {
if(qry(1,2)!=qry(2,1)) {
bool o=qry(1,3)^qry(3,1);
cout<<"! "<<(o?1:2)<<endl;
} else cout<<"! "<<3<<endl;
return ;
}
int a=qry(n,n-1),b=qry(n-1,n-2),c=qry(n-2,n);
if((a+b+c)&1) {
a^=qry(n-1,n),b^=qry(n-2,n-1);
cout<<"! "<<(a&&b?n-1:(a?n:n-2))<<endl;
return ;
} else n-=3;
}
int p=1,q=3;
for(int i=3;i<n;i+=2) if(qry(i,i+1)^qry(i+1,i)) {
p=i,q=1; break;
}
bool o=qry(p,q)^qry(q,p);
cout<<"! "<<(o?p:p+1)<<endl;
}
signed main() {
int T; cin>>T;
while(T--) solve();
return 0;
}
C. [CF2023D] Many Games
题目大意
给定
个物品 ,选出若干个元素,最大化 ,其中 。 数据范围:
。
思路分析
记
设
因此可以把元素个数优化到
从最优解中删除一个元素,不可能更优,故:
因此
然后按
时间复杂度
代码呈现
#include<bits/stdc++.h>
#define ld long double
using namespace std;
const int MAXN=2e5+5,Q=100;
vector <int> A[105];
int n,p[MAXN],w[MAXN];
ld f[MAXN];
signed main() {
scanf("%d",&n);
for(int i=1;i<=n;++i) scanf("%d%d",&p[i],&w[i]),A[p[i]].push_back(i);
vector <int> it;
for(int p=1;p<Q;++p) {
sort(A[p].begin(),A[p].end(),[&](int i,int j){ return w[i]>w[j]; });
int c=min((int)A[p].size(),Q/(Q-p)+1);
for(int i=0;i<c;++i) it.push_back(A[p][i]);
}
f[0]=1;
for(int i:it) for(int j=MAXN-1;j>=w[i];--j) {
f[j]=max(f[j],f[j-w[i]]*p[i]/100);
}
ld s=0,z=0;
for(int i:A[Q]) s+=w[i];
for(int j=0;j<MAXN;++j) z=max(z,(j+s)*f[j]);
printf("%.20Lf",z);
return 0;
}
D. [CF2025G] Variable Damage
题目大意
游戏里有
个人,每个人可以携带一件装备,假设共有 件装备,则每个人和装备每秒都会受到 的伤害。 每个人和装备都有血量,血量为
时会消失,一个人消失的时候他的装备也会消失。
次操作,每次加入一个装备或人,动态维护游戏至多能进行多久。 数据范围:
。
思路分析
我们只要统计每个东西能承受多少伤害,一个血量为
那么我们要求的就是最大匹配,先假设每个装备的贡献就是
因此我们就把这个问题转化为了数据结构问题:前缀加
感觉上
查询的时候,设当前块标记为
时间复杂度
代码呈现
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=3e5+5;
int n,m,a[MAXN],op[MAXN],st[MAXN],w[MAXN];
ll ans[MAXN],f[MAXN<<1],g[MAXN<<1];
signed main() {
ios::sync_with_stdio(false);
cin.tie(0),cout.tie(0);
cin>>m;
for(int i=1;i<=m;++i) cin>>op[i]>>a[i],st[++n]=a[i];
sort(st+1,st+n+1),n=unique(st+1,st+n+1)-st-1;
for(int i=1;i<=m;++i) a[i]=lower_bound(st+1,st+n+1,a[i])-st;
for(int i=1;i<=m;++i) ans[i]=ans[i-1]+st[a[i]];
for(int l=1,r,B=sqrt(n);l<=n;l=r+1) {
r=min(n,l+B-1);
memset(f,0,sizeof(f));
memset(g,0,sizeof(g));
for(int j=l;j<=r;++j) {
f[m-w[j]]+=st[j]-st[j-1];
g[m-w[j]]+=1ll*(st[j]-st[j-1])*w[j];
}
ll cnt=f[m],sum=g[m];
for(int i=1,tg=0;i<=m;++i) {
int x=a[i],c=(op[i]==1?-1:1);
if(x>=r) {
if(c==1) ++tg,cnt+=f[m+tg],sum+=g[m+tg];
else cnt-=f[m+tg],sum-=g[m+tg],--tg;
} else if(x>=l) {
cnt=0,sum=0;
for(int j=l;j<=r;++j) f[m-w[j]]=g[m-w[j]]=0;
for(int j=l;j<=r;++j) {
w[j]+=tg+(j<=x?c:0);
if(w[j]>=0) {
cnt+=st[j]-st[j-1];
sum+=1ll*(st[j]-st[j-1])*w[j];
}
f[m-w[j]]+=st[j]-st[j-1];
g[m-w[j]]+=1ll*(st[j]-st[j-1])*w[j];
}
tg=0;
}
ans[i]-=sum+tg*cnt;
}
}
for(int i=1;i<=m;++i) cout<<ans[i]<<"\n";
return 0;
}
*E. [CF2023E] Tree of Life
题目大意
给定
棵点的树,选出最少的简单路径,使得每对相邻的边都至少在一条路径中相邻出现过。 数据范围:
。
思路分析
先选出所有长度为
对于每个子树
那么我们的思路就是,对于
如果
但是此时我们发现这个做法不一定最优,因为当我们处理完
但我们无法在
很显然在
我们已经知道每个子树还有多少条剩余的路径,只要两两匹配即可,当且仅当这些路径数不存在绝对众数时有解。
但如果存在绝对众数,我们依然可以通过拆掉其他子树内的
时间复杂度
代码呈现
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=5e5+5;
vector <int> G[MAXN];
ll f[MAXN],g[MAXN],ans;
void dfs(int u,int fz) {
int s=G[u].size()-1;
ans+=1ll*s*(s+1)/2;
array <ll,2> mx={0,0};
ll p=0;
for(int v:G[u]) if(v^fz) {
dfs(v,u);
if(f[v]<s) {
ll z=min(g[v],(s-f[v]+1)/2);
g[v]-=z,f[v]+=2*z;
}
if(f[v]>s) {
ans-=s,f[v]-=s,p+=f[v];
mx=max(mx,{f[v],g[v]});
} else ans-=f[v];
g[u]+=g[v];
}
if(p<mx[0]*2) {
ll z=min(g[u]-mx[1],(mx[0]*2-p+1)/2);
g[u]-=z,p+=2*z;
}
ll z=min(p/2,p-mx[0]);
f[u]=s+p-2*z,g[u]+=z;
}
void solve() {
int n;
scanf("%d",&n),ans=0;
for(int i=1;i<=n;++i) f[i]=g[i]=0,G[i].clear();
for(int i=1,u,v;i<n;++i) scanf("%d%d",&u,&v),G[u].push_back(v),G[v].push_back(u);
dfs(1,0),ans-=g[1],printf("%lld\n",ans);
}
signed main() {
int T; scanf("%d",&T);
while(T--) solve();
return 0;
}
*F. [CF2023F] Hills and Pits
题目大意
给定
个沙坑,高度为 ,有正有负。 你可以在
数轴上左右游走,你手中初始没有沙子,经过 的沙坑可以取走 个沙子,经过 的沙坑可以放入 个沙子,要求每个沙坑上恰好有 个沙子,求最小步数。
次询问,对 求答案。 数据范围:
。
思路分析
首先分析答案下界,如果
如果想要答案更小,我们就要避免每个位置都经过两次,不妨假设路径是从
此时我们对路径是
因此此时的权值是
所以答案就是最大子段和,但
设
时间复杂度
代码呈现
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=3e5+5;
struct info {
int su,mx,lx,rx;
inline friend info operator +(const info &u,const info &v) {
return {u.su+v.su,max({u.mx,v.mx,u.rx+v.lx}),max(u.lx,u.su+v.lx),max(v.rx,v.su+u.rx)};
}
};
struct zKyGt1 {
info tr[1<<20];
int N;
void init(int n) {
for(N=1;N<=n;N<<=1);
for(int i=1;i<2*N;++i) tr[i]={0,0,0,0};
for(int i=1;i<=n;++i) tr[i+N]={-1,0,-1,-1};
for(int i=N-1;i;--i) tr[i]=tr[i<<1]+tr[i<<1|1];
}
void upd(int x) {
for(tr[x+=N]={1,1,1,1},x>>=1;x;x>>=1) tr[x]=tr[x<<1]+tr[x<<1|1];
}
int qry(int l,int r) {
info sl={0,0,0,0},sr={0,0,0,0};
for(l+=N-1,r+=N+1;l^r^1;l>>=1,r>>=1) {
if(~l&1) sl=sl+tr[l^1];
if(r&1) sr=tr[r^1]+sr;
}
return (sl+sr).mx;
}
} T;
ll a[MAXN],s[MAXN];
int n,m,l[MAXN],r[MAXN],ans[MAXN];
vector <int> qy[MAXN];
void sol() {
T.init(n);
vector <int> id;
for(int i=1;i<=n;++i) qy[i].clear(),s[i]=s[i-1]+a[i];
for(int i=1;i<=m;++i) if(l[i]<r[i]) qy[l[i]].push_back(i);
for(int i=0;i<=n;++i) id.push_back(i);
sort(id.begin(),id.end(),[&](int i,int j){ return s[i]^s[j]?s[i]>s[j]:i>j; });
for(int i:id) {
if(i>0) T.upd(i);
for(auto q:qy[i+1]) {
ans[q]=min(ans[q],2*(r[q]-l[q])-T.qry(l[q],r[q]-1));
}
}
}
void solve() {
cin>>n>>m;
for(int i=1;i<=n;++i) cin>>a[i];
for(int i=1;i<=m;++i) cin>>l[i]>>r[i],ans[i]=2*(r[i]-l[i]);
sol();
reverse(a+1,a+n+1);
for(int i=1;i<=m;++i) swap(l[i],r[i]),l[i]=n-l[i]+1,r[i]=n-r[i]+1;
sol();
for(int i=1;i<=m;++i) cout<<(s[r[i]]<s[l[i]-1]?-1:ans[i])<<"\n";
}
signed main() {
ios::sync_with_stdio(false);
cin.tie(0),cout.tie(0);
int _; cin>>_;
while(_--) solve();
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】