2025 省选做题记录(一)
Round #33 - 2024.12.3
A. [CF1981E] Turtle and Intersected Segments
题目大意
给定
条线段 ,有权值 ,如果 与 交非空,则在 之间连一条 权值的边,求图的 MST。 数据范围:
。
思路分析
注意到如果
因此我们只要对于每个
扫描线维护 std::set
维护集合,在插入删除的时候对前驱后继连边,此时边只有
时间复杂度
代码呈现
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=5e5+5,inf=1e9+7;
int n,a[MAXN],dsu[MAXN];
int find(int x) { return dsu[x]^x?dsu[x]=find(dsu[x]):x; }
void solve() {
cin>>n;
vector <array<int,2>> sg;
vector <array<int,3>> edg;
for(int i=1,l,r;i<=n;++i) {
cin>>l>>r>>a[i],dsu[i]=i;
sg.push_back({l,-i}),sg.push_back({r,i});
}
set <array<int,2>> id;
id.insert({0,0}),id.insert({inf,0});
auto pre=[&](int i) {
return (*--id.lower_bound({a[i],i}))[1];
};
auto suf=[&](int i) {
return (*id.upper_bound({a[i],i}))[1];
};
auto link=[&](int u,int v) {
if(u&&v) edg.push_back({abs(a[u]-a[v]),u,v});
};
sort(sg.begin(),sg.end());
for(auto it:sg) {
int i=it[1];
if(i<0) i*=-1,link(pre(i),i),link(suf(i),i),id.insert({a[i],i});
else id.erase({a[i],i}),link(pre(i),suf(i));
}
sort(edg.begin(),edg.end());
int cnt=0; ll ans=0;
for(auto &e:edg) {
if(find(e[1])!=find(e[2])) dsu[find(e[1])]=find(e[2]),ans+=e[0],++cnt;
}
cout<<(cnt==n-1?ans:-1)<<"\n";
}
signed main() {
ios::sync_with_stdio(false);
int T; cin>>T;
while(T--) solve();
return 0;
}
B. [CF1979F] Kostyanych's Theorem
题目大意
交互器有一张
个点的图,边数 ,每次交互可以给定 ,选出 的所有点中度数最小的一个 ,以及不为 邻居的点中度数最小的一个点,然后删除 。 求出图的一条哈密尔顿路。
数据范围:
。
思路分析
首先图的边数很多,根据鸽巢原理,度数最大点的度数
如果存在一个度数为
构造剩余图的哈密尔顿路
容易发现上述过程只要在边数
如果不存在这样的点,那么存在一个度数为
很显然图上度数最小的点度数
用 std::deque
维护哈密尔顿路径。
时间复杂度
代码呈现
#include<bits/stdc++.h>
using namespace std;
const int MAXN=1e5+5;
int n;
bool vis[MAXN];
deque <int> wys;
void dfs(int s) {
if(s<=2) {
for(int i=1;i<=n;++i) if(!vis[i]) wys.push_back(i);
return ;
}
cout<<"? "<<s-2<<endl;
int u,v;
cin>>u>>v;
if(v) {
vis[u]=true,dfs(s-1);
if(wys.front()!=v) wys.push_front(u);
else wys.push_back(u);
} else {
int p,q;
cout<<"? "<<0<<endl;
cin>>p>>q;
vis[u]=vis[p]=true,dfs(s-2);
wys.push_back(u),wys.push_back(p);
}
}
void solve() {
cin>>n,wys.clear();
dfs(n),cout<<"! ";
for(int i:wys) cout<<i<<" "; cout<<endl;
for(int i=1;i<=n;++i) vis[i]=false;
}
signed main() {
ios::sync_with_stdio(false);
int T; cin>>T;
while(T--) solve();
return 0;
}
C. [CF1981F] Turtle and Paths on a Tree
题目大意
给定
个点的二叉树,点带权,将树上的边分成若干条路径,最小化每条路径上点权 的和。 数据范围:
.
思路分析
先刻画
那么
于是可以 dp,设
转移的时候尝试把左右子树的链闭合起来,或者把两条链在
时间复杂度
但是我们观察到答案中不会存在
对于一条
可以把链划分成
此时
因此权值为
可以证明
时间复杂度
代码呈现
#include<bits/stdc++.h>
using namespace std;
const int MAXN=25005,B=4000,inf=1e9;
int n,f[MAXN][B+5],a[MAXN];
vector <int> G[MAXN];
void dfs(int u) {
if(G[u].empty()) {
for(int i=1;i<=B;++i) f[u][i]=(i==a[u]?inf:0);
return ;
}
if(G[u].size()==1) {
int mn=inf,v=G[u][0];
dfs(v);
for(int i=1;i<=B;++i) if(i!=a[u]) mn=min(mn,f[v][i]+i);
for(int i=1;i<=B;++i) f[u][i]=(i==a[u]?inf:min(f[v][i],mn));
if(u==1) cout<<mn<<"\n";
return ;
}
int x=G[u][0],y=G[u][1];
dfs(x),dfs(y);
int mn=inf,mnx=inf,mny=inf;
for(int i=1;i<=B;++i) if(i!=a[u]) mn=min(mn,f[x][i]+f[y][i]+i),mnx=min(mnx,f[x][i]+i),mny=min(mny,f[y][i]+i);
for(int i=1;i<=B;++i) f[u][i]=(i==a[u]?inf:min({f[x][i]+mny,f[y][i]+mnx,mn}));
if(u==1) cout<<min(mn,mnx+mny)<<"\n";
}
void solve() {
cin>>n;
for(int i=1;i<=n;++i) cin>>a[i],G[i].clear(),fill(f[i]+1,f[i]+B+1,inf);
for(int i=2,fz;i<=n;++i) cin>>fz,G[fz].push_back(i);
dfs(1);
}
signed main() {
ios::sync_with_stdio(false);
int T; cin>>T;
while(T--) solve();
return 0;
}
*D. [CF1975H] 378QAQ and Core
题目大意
给定长度为
的字符串 ,重排 以最小化 的最大字典序子串。 数据范围:
。
思路分析
首先一个字符串的最大字典序子串一定是后缀,且由最大字符
同理末尾不为
因此可以调整使得开头结尾都是
假设
因此我们只需要确定所有
并且根据朴素贪心,如果当前的最大后缀从
如果
那么每个
对于这种情况,显然每个字符串的开头一定是字符集中最小的
容易发现每次两次递归后
时间复杂度
代码呈现
#include<bits/stdc++.h>
using namespace std;
string core(vector <string> S) {
if(S.empty()) return "";
int n=S.size(),x=1;
string z=S[n-1];
for(int i=n-2;i>=0&&S[i]==z;--i) ++x;
if(x==1) return z;
int y=n-x;
if(y>=x) {
int w=x-1,len=w;
vector <string> T(w,z);
for(int i=0;i<y;) {
int nw=len;
for(int p=w-len;p<w&&i<y;++p,++i) {
T[p]+=S[i];
if(p+1<w&&S[i]<S[i+1]) nw=w-p-1;
}
len=nw;
}
return core(T)+z;
}
vector <string> T;
int k=x/(y+1);
string kz="";
for(int i=0;i<k;++i) kz+=z;
for(int i=0;i<y;++i) T.push_back(kz+S[i]);
for(int i=0;i<x%(y+1);++i) T.push_back(z);
return core(T)+kz;
}
void solve() {
int n; string s;
cin>>n>>s;
vector <string> S;
for(auto c:s) S.push_back(string(1,c));
sort(S.begin(),S.end());
cout<<core(S)<<"\n";
}
signed main() {
ios::sync_with_stdio(false);
int T; cin>>T;
while(T--) solve();
return 0;
}
*E. [CF1975I] Mind Bloom
题目大意
给定
张牌,第 张有数字 ,初始手牌为 ,每次可以打出手中数字最大的牌 ,从剩余的牌中等概率抽 张加入手牌,求抽出所有牌的概率。 数据范围:
。
思路分析
将
考虑划分状态,我们发现初始手牌一定按从大到小的顺序被打出,且如果
因此我们可以按
设计状态
假设
转移时枚举
那么我们要求的就是转移系数
计算的时候再做一遍求答案的 dp,
转移
从大到小 dp,由
处理出
时间复杂度
代码呈现
#include<bits/stdc++.h>
using namespace std;
const int MAXN=125,MOD=1e9+7;
int ksm(int a,int b=MOD-2) { int s=1; for(;b;a=1ll*a*a%MOD,b>>=1) if(b&1) s=1ll*s*a%MOD; return s; }
int n,a[MAXN],c[MAXN],inv[MAXN],op[MAXN];
int f[MAXN][MAXN][MAXN],g[MAXN][MAXN],h[MAXN];
int dp[MAXN][MAXN];
void F(int u,int s) { //max=u, cnt[1,u-1] = s
if(!a[u]) return f[u][s][s]=1,void();
if(a[u]+s>=n) return ;
memset(g,0,sizeof(g));
memset(h,0,sizeof(h));
g[n][a[u]+s]=1;
for(int i=n;i>=u;--i) for(int j=i;j>=a[u]+s;--j) { //max<=i, cnt=j
const int p=1ll*(j-s)*inv[i-s]%MOD,z=g[i][j],w=1ll*p*z%MOD;
if(!z) continue;
g[i-1][j]=(g[i-1][j]+1ll*(1+MOD-p)*z)%MOD;
for(int k=j+a[i]-1;k<i;++k) {
if(i==u&&j==s+1) h[k]=(h[k]+w)%MOD;
else g[i-1][k]=(g[i-1][k]+1ll*w*f[i][j-1][k])%MOD;
}
}
for(int i=s;i<u;++i) f[u][s][i]=1ll*g[u-1][i]*ksm(1+MOD-h[i])%MOD;
}
void solve() {
scanf("%d",&n);
for(int i=1;i<=n;++i) scanf("%d",&a[i]);
for(int i=1;i<=n;++i) scanf("%1d",&op[i]),c[i]=c[i-1]+op[i];
if(c[n]==n) return puts("1"),void();
if(c[n]==0) return puts("0"),void();
if(a[n]<=1) return puts("0"),void();
if(a[1]>=1) return puts("1"),void();
memset(f,0,sizeof(f));
for(int i=n;i;--i) for(int j=i-1;~j;--j) F(i,j);
memset(dp,0,sizeof(dp));
dp[0][0]=1;
for(int i=1;i<=n;++i) for(int j=c[i];j<=i;++j) { //max<=i, cnt=j
for(int k=0;k<i;++k) {
dp[i][j]=(dp[i][j]+1ll*f[i][j-1][k]*dp[i-1][k])%MOD;
}
if(!op[i]) {
const int p=1ll*(j-c[i])*inv[i-c[i]]%MOD;
dp[i][j]=(1ll*(1+MOD-p)*dp[i-1][j]+1ll*p*dp[i][j])%MOD;
}
}
printf("%d\n",(1+MOD-dp[n][c[n]])%MOD);
}
signed main() {
inv[1]=1;
for(int i=2;i<MAXN;++i) inv[i]=1ll*inv[MOD%i]*(MOD-MOD/i)%MOD;
int T; scanf("%d",&T);
while(T--) solve();
return 0;
}
Round #34 - 2024.12.4
A. [CF1982F] Sorting Problem Again
题目大意
给定
, 次操作单点修改,或查询长度最小的 使得对 排序后原序列有序。 数据范围:
。
思路分析
不妨假设
可以用线段树维护信息,维护区间左右端点值,最长的满足
如果
用平衡树维护。
时间复杂度
代码呈现
#include<bits/stdc++.h>
using namespace std;
const int MAXN=5e5+5;
namespace IO {
int ow,olim=(1<<23)-100;
char buf[1<<23],*p1=buf,*p2=buf,obuf[1<<23];
#define gc() (p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<23,stdin),p1==p2)?EOF:*p1++)
int read() {
int x=0,f=1; char c=gc();
while(!isdigit(c)) f=(c=='-'?-f:f),c=gc();
while(isdigit(c)) x=x*10+(c^48),c=gc();
return x*f;
}
void flush() {
fwrite(obuf,ow,1,stdout),ow=0;
}
void write(int x) {
if(!x) obuf[ow++]='0';
else {
int t=ow;
for(;x;x/=10) obuf[ow++]=(x%10)^48;
reverse(obuf+t,obuf+ow);
}
if(ow>=olim) flush();
}
void putc(char c) {
obuf[ow++]=c;
if(ow>=olim) flush();
}
void putstr(const string &s) {
for(auto &c:s) obuf[ow++]=c;
if(ow>=olim) flush();
}
#undef gc
}
mt19937 rnd(time(0));
int n,q,rt,a[MAXN];
struct Treap {
struct Node {
array<int,2> val;
int pri,ls,rs,siz,lv,rv,lx,rx;
} tr[MAXN];
inline void psu(int u) {
const Node &x=tr[tr[u].ls],&y=tr[tr[u].rs];
Node &t=tr[u];
if(!t.ls&&!t.rs) {
t.siz=1;
t.lv=t.rv=u;
t.lx=t.rx=1;
return ;
}
if(!t.ls) {
t.siz=1+y.siz;
t.lv=u,t.rv=y.rv;
if(y.lv==u+1) {
t.lx=1+y.lx;
t.rx=y.rx+(y.rx==y.siz);
} else {
t.lx=1;
t.rx=y.rx;
}
return ;
}
if(!t.rs) {
t.siz=x.siz+1;
t.lv=x.lv,t.rv=u;
if(x.rv==u-1) {
t.lx=x.lx+(x.lx==x.siz);
t.rx=1+x.rx;
} else {
t.lx=x.lx;
t.rx=1;
}
return ;
}
t.siz=x.siz+1+y.siz;
t.lv=x.lv,t.rv=y.rv;
if(x.rv==u-1&&x.lx==x.siz) {
if(y.lv==u+1) t.lx=x.siz+1+y.lx;
else t.lx=x.siz+1;
} else t.lx=x.lx;
if(y.lv==u+1&&y.rx==y.siz) {
if(x.rv==u-1) t.rx=y.siz+1+x.rx;
else t.rx=y.siz+1;
} else t.rx=y.rx;
}
inline void init(int u) {
tr[u].val={a[u],u};
tr[u].pri=rnd();
tr[u].ls=tr[u].rs=0;
tr[u].siz=1;
tr[u].lv=tr[u].rv=u;
tr[u].lx=tr[u].rx=1;
}
void split(int u,array<int,2>k,int &x,int &y) {
if(!u) return x=y=0,void();
if(k<tr[u].val) return y=u,split(tr[u].ls,k,x,tr[u].ls),psu(u);
return x=u,split(tr[u].rs,k,tr[u].rs,y),psu(u);
}
int merge(int x,int y) {
if(!x||!y) return x|y;
if(tr[x].pri<tr[y].pri) return tr[x].rs=merge(tr[x].rs,y),psu(x),x;
else return tr[y].ls=merge(x,tr[y].ls),psu(y),y;
}
void dfs(int u) {
if(!u) return ;
dfs(tr[u].ls),dfs(tr[u].rs),psu(u);
}
void build() {
static int id[MAXN],stk[MAXN];
for(int i=1;i<=n;++i) id[i]=i;
sort(id+1,id+n+1,[&](int x,int y){ return tr[x].val<tr[y].val; });
int tp=0;
for(int o=1;o<=n;++o) {
int i=id[o];
while(tp&&tr[stk[tp]].pri>tr[i].pri) tr[i].ls=stk[tp--];
if(tp) tr[stk[tp]].rs=i;
stk[++tp]=i;
}
dfs(rt=stk[1]);
}
} T;
void ins(int i) {
int x,y;
T.split(rt,{a[i],i},x,y);
rt=T.merge(x,T.merge(i,y));
}
void ers(int i) {
int x,y,z,o;
T.split(rt,{a[i],i},o,z);
T.split(o,{a[i],i-1},x,y);
rt=T.merge(x,z);
}
void qry() {
auto o=T.tr[rt];
if(o.lx==n) return IO::putstr("-1 -1\n");
int l=(o.lv==1)?o.lx:0;
int r=(o.rv==n)?o.rx:0;
IO::write(l+1);
IO::putc(' ');
IO::write(n-r);
IO::putc('\n');
}
void solve() {
n=IO::read(),rt=0;
for(int i=1;i<=n;++i) a[i]=IO::read(),T.init(i);
T.build();
q=IO::read(),qry();
for(int p,v;q--;) {
p=IO::read(),v=IO::read(),ers(p),a[p]=v,T.init(p),ins(p),qry();
}
}
signed main() {
ios::sync_with_stdio(false);
int _=IO::read();
while(_--) solve();
IO::flush();
return 0;
}
B. [CF1987F2] Interesting Problem
题目大意
给定
,每次可以选定一个 的元素并删除 ,求最多操作次数。 数据范围:
。
思路分析
假如我们只删除
设
由于第
回到原问题,此时如果删除
因此我们可以把删除的元素看成若干个匹配的括号,容易发现每个连续的子段都是合法括号序列,以其为单位进行 dp。
我们求出
- 在外面加一对括号:即先删
,再删 。 - 连接两个括号序列:即并行删除
。
求出
时间复杂度
代码呈现
#include<bits/stdc++.h>
using namespace std;
const int MAXN=805;
int n,a[MAXN],f[MAXN][MAXN],dp[MAXN];
void solve() {
scanf("%d",&n);
for(int i=1;i<=n;++i) scanf("%d",&a[i]);
memset(f,0x3f,sizeof(f));
for(int i=0;i<=n;++i) f[i+1][i]=0;
for(int len=2;len<=n;len+=2) for(int l=1,r=len;r<=n;++l,++r) {
if((l-a[l])%2==0&&f[l+1][r-1]<=(l-a[l])/2) f[l][r]=(l-a[l])/2;
for(int k=l+1;k<r;k+=2) f[l][r]=min(f[l][r],max(f[l][k],f[k+1][r]-(k-l+1)/2));
}
memset(dp,0,sizeof(dp));
for(int i=1;i<=n;++i) {
dp[i]=dp[i-1];
for(int j=i-1;j>0;j-=2) {
if(dp[j-1]>=f[j][i]) dp[i]=max(dp[i],dp[j-1]+(i-j+1)/2);
}
}
printf("%d\n",dp[n]);
}
signed main() {
int T; scanf("%d",&T);
while(T--) solve();
return 0;
}
C. [CF1983G] Your Loss
题目大意
给定
个点的树,点有点权 , 次询问 ,设 路径为 ,求 。 数据范围:
。
思路分析
把询问拆成
先计算
先拆位,对每个
观察
那么我们可以设
那么求
每段
同理
时间复杂度
代码呈现
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=5e5+5,K=19;
int n,q,a[MAXN],dep[MAXN],fa[MAXN][K],c[MAXN][K];
int f[MAXN][K],g[MAXN][K];
ll dis[MAXN];
vector <int> G[MAXN];
void dfs(int u,int fz) {
dep[u]=dep[fz]+1,dis[u]=dis[fz]+a[u];
fa[u][0]=fz;
for(int k=0;k<K;++k) c[u][k]=c[fz][k]+(a[u]>>k&1?-1:1);
for(int k=1;k<K;++k) fa[u][k]=fa[fa[u][k-1]][k-1];
for(int k=0;k<K;++k) {
int x=fa[u][k],y=fa[fa[u][k]][k];
f[u][k]=f[y][k]+c[x][k]-c[y][k];
g[u][k]=g[y][k]+c[u][k]-c[x][k];
}
for(int v:G[u]) if(v^fz) dfs(v,u);
}
int LCA(int u,int v) {
if(dep[u]<dep[v]) swap(u,v);
for(int k=K-1;~k;--k) if(dep[fa[u][k]]>=dep[v]) u=fa[u][k];
if(u==v) return u;
for(int k=K-1;~k;--k) if(fa[u][k]^fa[v][k]) u=fa[u][k],v=fa[v][k];
return fa[u][0];
}
ll qf(int u,int d) {
ll ans=0;
for(int i=K-1;~i;--i) if(d>>i&1) {
int v=fa[u][i];
for(int j=0;j<i;++j) ans+=(1ll<<j)*(f[u][j]-f[v][j]);
for(int j=K-1;j>i;--j) if(d>>j&1) ans+=(1ll<<j)*(c[u][j]-c[v][j]);
u=v;
}
return ans;
}
ll qg(int u,int d) {
ll ans=0;
for(int i=0;i<K;++i) if(d>>i&1) {
int v=fa[u][i];
for(int j=0;j<i;++j) ans+=(1ll<<j)*(g[u][j]-g[v][j]);
for(int j=i+1;j<K;++j) if(d>>j&1) ans+=(1ll<<j)*(c[u][j]-c[v][j]);
u=v;
}
return ans;
}
void solve() {
cin>>n;
for(int i=1,u,v;i<n;++i) cin>>u>>v,G[u].push_back(v),G[v].push_back(u);
for(int i=1;i<=n;++i) cin>>a[i];
G[0].push_back(1),a[0]=0,dfs(0,0);
cin>>q;
for(int u,v,w;q--;) {
cin>>u>>v,w=LCA(u,v);
ll ans=qf(u,dep[u]-dep[w]+1);
if(w!=v) ans+=qg(v,dep[u]+dep[v]-2*dep[w]+1)-qg(w,dep[u]-dep[w]+1);
cout<<ans+dis[u]+dis[v]-dis[w]*2+a[w]<<"\n";
}
for(int i=0;i<=n;++i) G[i].clear();
}
signed main() {
ios::sync_with_stdio(false);
int T; cin>>T;
while(T--) solve();
return 0;
}
*D. [CF1984G] Magic Trick II
题目大意
给定
排列 ,选定 ,每次操作可以把 的一个长度为 的子段移动到任意位置,求最大的 使得可以通过这个操作给 排序,构造方案。 数据范围:
。
思路分析
我们发现
我们考虑
- 显然能任意循环移位
。 - 如果
是奇数,那么可以任意循环移位 。
因此当
从小到大枚举
构造很简单,先循环移位整个排列,把
如果
但此时我们依然能将
但这样操作结束的时候可能会得到
那么我们不得不取
时间复杂度
代码呈现
#include<bits/stdc++.h>
using namespace std;
vector <array<int,2>> wys;
const int MAXN=1005;
int n,a[MAXN],z;
bool spj() {
for(int i=2;i<=n;++i) if(a[i]!=a[i-1]%n+1) return false;
if(a[1]==1) z=n;
else {
z=n-1;
for(int i=1;a[i]!=1;++i) wys.push_back({2,1});
}
return true;
}
void rot() { wys.push_back({3,1}),rotate(a+1,a+3,a+n+1); }
void rotL() { wys.push_back({2,1}),rotate(a+1,a+2,a+n); }
void rotR() { wys.push_back({3,2}),rotate(a+2,a+3,a+n+1); }
void build() {
z=n-2;
for(int i=1;i<=n;++i) {
while(a[1]!=i) rot();
if(i>1) while(a[n]!=i-1) rotR();
}
while(a[1]!=1) rot();
}
void solve() {
cin>>n,wys.clear();
for(int i=1;i<=n;++i) cin>>a[i];
if(spj()) return ;
if(n%2==1) return build();
int inv=0;
for(int i=1;i<=n;++i) for(int j=i+1;j<=n;++j) if(a[i]>a[j]) ++inv;
if(inv%2==1) {
while(a[n]!=n) {
int x=find(a+1,a+n+1,n)-a;
x=min(x,3),wys.push_back({x,x+1});
rotate(a+x,a+x+n-3,a+x+n-2);
}
return --n,build();
}
z=n-2;
for(int i=1;i<=n;++i) {
while(a[1]!=i&&a[n]!=i) rot();
if(i==1) continue;
if(a[1]==i) while(a[n]!=i-1) rotR();
else while(a[n-1]!=i-1) rotL();
}
while(a[1]!=1) rot();
}
signed main() {
ios::sync_with_stdio(false);
int T; cin>>T;
while(T--) {
solve();
cout<<z<<"\n"<<wys.size()<<"\n";
for(auto o:wys) cout<<o[0]<<" "<<o[1]<<"\n";
}
return 0;
}
E. [CF1987H] Fumo Temple
题目大意
交互器有
矩阵 ,其中 ,存在特殊点 ,每次询问 会得到 。 在
次询问内求出 。 数据范围:
。
思路分析
交互器返回的答案非常奇怪,不妨看成
考虑随机化,动态维护可能成为答案的点集,每次随机询问一个点,然后删除不可能成为答案的点。
交互次数和复杂度未知,但可以通过。
代码呈现
#include<bits/stdc++.h>
using namespace std;
mt19937 rnd(time(0));
vector <array<int,2>> S,T;
void solve() {
int n,m;
cin>>n>>m,S.clear(),T.clear();
for(int i=1;i<=n;++i) for(int j=1;j<=m;++j) S.push_back({i,j});
while(true) {
auto z=S[rnd()%S.size()];
cout<<"? "<<z[0]<<" "<<z[1]<<endl;
int w; cin>>w;
if(!w) return cout<<"! "<<z[0]<<" "<<z[1]<<endl,void();
for(auto &p:S) {
int x=abs(p[0]-z[0]),y=abs(p[1]-z[1]);
if(x+y<=w&&w<=x+y+(x+1)*(y+1)) T.push_back(p);
}
S.swap(T),T.clear();
}
}
signed main() {
S.resize(5000*5000),T.resize(5000*5000);
int _; cin>>_;
while(_--) solve();
return 0;
}
Round #35 - 2024.12.5
A. [CF1990E2] Catch the Mole(Hard Version)
题目大意
给定
个点的树,交互器有一个点 ,每次可以询问 ,可以知道 是否在 子树中,如果不在,就令 ,在 次操作后确定 的位置。 数据范围:
。
思路分析
考虑动态维护可能成为
对每个
但这个策略显然不优,在最开始的几次可能被诱导进入不优的点导致后续无解。
因此我们在最开始的若干次操作直接在
时间复杂度
代码呈现
#include<bits/stdc++.h>
using namespace std;
const int MAXN=5005;
mt19937 rnd(time(0));
int n,q,rt,siz[MAXN],slf[MAXN],w[MAXN],fa[MAXN];
vector <int> G[MAXN],que;
bool vis[MAXN],nv[MAXN];
void dfs0(int u,int fz) {
fa[u]=fz;
for(int v:G[u]) if(v^fz) dfs0(v,u);
if(fz) G[u].erase(find(G[u].begin(),G[u].end(),fz));
}
void dfs1(int u) {
que.push_back(u),siz[u]=1,slf[u]=G[u].empty();
for(int v:G[u]) if(vis[v]) dfs1(v),siz[u]+=siz[v],slf[u]+=slf[v];
}
int ch[MAXN<<4];
void dfs2(int u) {
for(int v:G[u]) if(vis[v]&&v!=q) dfs2(v),nv[u]=true;
}
void dfs3(int u) {
nv[u]=true;
for(int v:G[u]) if(vis[v]) dfs3(v);
}
void solve() {
cin>>n,rt=1;
for(int i=1;i<=n;++i) G[i].clear(),vis[i]=true;
for(int i=1,u,v;i<n;++i) cin>>u>>v,G[u].push_back(v),G[v].push_back(u);
dfs0(1,0);
for(int _=0;;++_) {
que.clear(),dfs1(rt);
if(que.size()==1) return cout<<"! "<<rt<<endl,void();
int o;
if(_<=20) q=que[rnd()%que.size()];
else {
int mw=n+1,tp=0;
for(int u:que) {
w[u]=max(siz[u],(siz[rt]-siz[u])-(slf[rt]-slf[u])+(rt!=1));
mw=min(mw,w[u]);
}
for(int u:que) if(w[u]<=mw+4) {
for(int x=0;x<(1<<(mw+4-w[u]));++x) ch[tp++]=u;
}
q=ch[rnd()%tp];
}
cout<<"? "<<q<<endl,cin>>o;
fill(nv+1,nv+n+1,false);
if(!o) {
if(rt!=1) rt=fa[rt];
dfs2(rt);
} else {
dfs3(rt=q);
}
copy(nv+1,nv+n+1,vis+1);
}
}
signed main() {
int T; cin>>T;
while(T--) solve();
return 0;
}
*B. [CF1990F] Polygonal Segments
题目大意
给定
, 次操作单点修改或者查询 内最长的子区间 使得存在一个边长恰好为 的多边形。 数据范围:
。
思路分析
很显然
观察发现如果
单次查询可以 CDQ 分治,把
我们发现一个后缀
很显然这样的后缀只有
那么动态版本就用线段树维护每个区间的候选前缀与候选后缀,合并的时候考虑最大值来自左边还是右边,双指针在另外一边求出尽可能长的前缀 / 后缀。
时间复杂度
代码呈现
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=2e5+5;
int n,q;
ll a[MAXN];
struct seg { int x; ll s; };
struct info {
int l,r,ans;
ll sum;
vector <seg> pr,sf;
info() { l=r=sum=0,ans=-1,pr.clear(),sf.clear(); }
info(int i) { l=r=i,ans=-1,sum=a[i],pr=sf={{i,0}}; }
inline friend info operator +(const info &L,const info &R) {
if(!L.l||!R.r) return L.l?L:R;
info X;
X.l=L.l,X.r=R.r,X.sum=L.sum+R.sum;
X.ans=max(L.ans,R.ans),X.pr=L.pr,X.sf=R.sf;
for(auto i:R.pr) {
if(a[i.x]>=L.sum+i.s) X.pr.push_back({i.x,L.sum+i.s});
}
for(auto i:L.sf) {
if(a[i.x]>=i.s+R.sum) X.sf.push_back({i.x,i.s+R.sum});
}
vector <seg> vl=L.sf,vr=R.pr;
int sl=vl.size(),sr=vr.size();
vl.push_back({L.l-1,L.sum});
vr.push_back({R.r+1,R.sum});
auto chk=[&](int i,int j) {
if(max(a[vl[i].x],a[vr[j].x])*2<vl[i+1].s+vr[j+1].s) {
X.ans=max(X.ans,vr[j+1].x-vl[i+1].x-1);
}
};
for(int i=0,j=-1;i<sl;++i) {
for(;j+1<sr&&a[vl[i].x]>=a[vr[j+1].x];++j);
if(j>=0) chk(i,j);
}
for(int i=0,j=-1;i<sr;++i) {
for(;j+1<sl&&a[vr[i].x]>=a[vl[j+1].x];++j);
if(j>=0) chk(j,i);
}
return X;
}
};
struct zkwSegt {
info tr[1<<19];
int N;
void init() {
for(N=1;N<=n;N<<=1);
for(int i=1;i<(N<<1);++i) tr[i]=info();
for(int i=1;i<=n;++i) tr[i+N]=info(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]=info(x),x=(x+N)>>1;x;x>>=1) tr[x]=tr[x<<1]+tr[x<<1|1];
}
int qry(int l,int r) {
info sl=info(l),sr=info(r);
for(l+=N,r+=N;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).ans;
}
} T;
void solve() {
cin>>n>>q;
for(int i=1;i<=n;++i) cin>>a[i];
T.init();
for(ll op,x,y;q--;) {
cin>>op>>x>>y;
if(op==1) cout<<T.qry(x,y)<<"\n";
else a[x]=y,T.upd(x);
}
}
signed main() {
ios::sync_with_stdio(false);
int _; cin>>_;
while(_--) solve();
return 0;
}
C. [CF1988F] Heartbeat
题目大意
设排列
有 个前缀最大值, 个后缀最大值, 个上升,权值为 。 对于
,求出所有 阶排列权值和。 数据范围:
。
思路分析
我们发现前缀最大值一定在最大值左边,后缀最大值一定在最大值右边,因此可以把排列分成两端。
考虑排列的左半部分,要统计的问题是
转移的时候考虑插入最小值:
- 插在开头:
。 - 插在上升或末尾:
。 - 插在下降位置:
。
然后需要合并前后两部分,此时前缀 / 后缀最大值的贡献已经解决,只要记录上升个数,可以处理出
那么转移就是
朴素转移复杂度过高,可以分步,特判
时间复杂度
代码呈现
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=705,MOD=998244353;
int n;
ll a[MAXN],b[MAXN],c[MAXN],C[MAXN][MAXN],dp[MAXN];
ll f[MAXN][MAXN],g[MAXN][MAXN],u[MAXN][MAXN],v[MAXN][MAXN];
signed main() {
ios::sync_with_stdio(false);
cin>>n;
for(int i=0;i<=n;++i) for(int j=C[i][0]=1;j<=i;++j) C[i][j]=(C[i-1][j]+C[i-1][j-1])%MOD;
for(int i=1;i<=n;++i) cin>>a[i];
for(int i=1;i<=n;++i) cin>>b[i];
for(int i=0;i<n;++i) cin>>c[i];
f[0][1]=1;
for(int i=1;i<n;++i) {
for(int j=0;j<i;++j) for(int k=1;k<=i;++k) {
u[i][j]=(u[i][j]+f[j][k]*a[k+1])%MOD;
v[i][j]=(v[i][j]+f[i-1-j][k]*b[k+1])%MOD;
}
if(i==n-1) break;
memset(g,0,sizeof(g));
for(int j=0;j<i;++j) for(int k=1;k<=i;++k) {
const ll w=f[j][k];
if(!w) continue;
g[j+1][k+1]=(g[j+1][k+1]+w)%MOD;
g[j][k]=(g[j][k]+w*(j+1))%MOD;
g[j+1][k]=(g[j+1][k]+w*(i-1-j))%MOD;
}
memcpy(f,g,sizeof(f));
}
u[0][0]=a[1],v[0][0]=b[1];
for(int j=0;j<n;++j) for(int y=0;y<=j;++y) dp[j+1]=(dp[j+1]+a[1]*v[j][y]%MOD*c[y])%MOD;
for(int j=0;j<n;++j) for(int x=0;x<n;++x) {
ll sum=0;
for(int y=0;y<=j;++y) sum=(sum+v[j][y]*c[x+y+1])%MOD;
for(int i=x;i+j<n;++i) if(i>0) {
dp[i+j+1]=(dp[i+j+1]+u[i][x]*sum%MOD*C[i+j][j])%MOD;
}
}
for(int i=1;i<=n;++i) cout<<dp[i]<<" \n"[i==n];
return 0;
}
D. [CF1989F] Simultaneous Coloring
题目大意
棋盘,每次操作可以选择 个行或列,给行染红,列染蓝,代价 。
次询问,每次告诉你一个位置最终的颜色,求得到满足当前限制的棋盘的最小代价。 数据范围:
。
思路分析
设第
把所有限制关系画成图,如果有强联通分量,说明强连通分量中的点一定相等,不同强连通分量中的点可以按拓扑序操作。
由于答案有凸性,因此合并两个强联通分量一定不优,答案就是每个强连通分量大小的平方和,我们就是要在
这种动态连通性问题,考虑整体二分,对每条边
那么我们加入时间
分别把这些点递归到
时间复杂度
代码呈现
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=4e5+5;
vector <int> G[MAXN];
int dfn[MAXN],low[MAXN],dcnt,stk[MAXN],tp,bel[MAXN],scnt;
bool ins[MAXN];
void tarjan(int u) {
dfn[u]=low[u]=++dcnt,stk[++tp]=u,ins[u]=true;
for(int v:G[u]) {
if(!dfn[v]) tarjan(v),low[u]=min(low[u],low[v]);
else if(ins[v]) low[u]=min(low[u],dfn[v]);
}
if(low[u]==dfn[u]) {
++scnt;
while(ins[u]) bel[stk[tp]]=scnt,ins[stk[tp--]]=false;
}
}
struct Edge { int u,v,t; };
int R,C,n,q,dsu[MAXN],siz[MAXN];
ll ans=0;
ll W(int k) { return k>1?1ll*k*k:0; }
int find(int x) { return dsu[x]^x?dsu[x]=find(dsu[x]):x; }
void merge(int u,int v) {
u=find(u),v=find(v);
if(u==v) return ;
ans-=W(siz[u])+W(siz[v]);
siz[u]+=siz[v],dsu[v]=u,ans+=W(siz[u]);
}
void solve(int l,int r,vector<Edge>&E) {
if(E.empty()) {
for(int i=l;i<=min(r,q);++i) cout<<ans<<"\n";
return ;
}
if(l==r) {
for(auto e:E) merge(e.u,e.v);
if(l<=q) cout<<ans<<"\n";
return ;
}
int mid=(l+r)>>1;
vector <Edge> LE,RE;
for(auto &e:E) {
e.u=find(e.u),e.v=find(e.v);
if(e.t<=mid) G[e.u].push_back(e.v);
}
for(auto e:E) {
if(e.t<=mid) {
if(!dfn[e.u]) tarjan(e.u);
if(!dfn[e.v]) tarjan(e.v);
(bel[e.u]==bel[e.v]?LE:RE).push_back(e);
} else RE.push_back(e);
}
dcnt=scnt=0;
for(auto e:E) if(e.t<=mid) dfn[e.u]=dfn[e.v]=0,G[e.u].clear(),G[e.v].clear();
solve(l,mid,LE),solve(mid+1,r,RE);
}
signed main() {
ios::sync_with_stdio(false);
cin>>R>>C>>q,n=R+C;
for(int i=1;i<=n;++i) dsu[i]=i,siz[i]=1;
vector <Edge> E;
for(int i=1,u,v;i<=q;++i) {
char op;
cin>>u>>v>>op,v+=R;
if(op=='R') swap(u,v);
E.push_back({u,v,i});
}
solve(1,q+1,E);
return 0;
}
*E. [CF1987G2] Spinning Round
题目大意
给定
排列 ,定义 表示 左侧 / 右侧第一个 的元素(没有设成 )。 每个点向
连边,有一些点已经确定连边,使得图连通并最大化直径。 数据范围:
。
思路分析
建出大根笛卡尔树,那么
很显然根节点一定会连自己,那么剩下的边必须构成数,那么根的左链必须连
考虑树上线头 dp,对于每个
设
转移的时候分讨
查询的时候枚举
时间复杂度
代码呈现
#include<bits/stdc++.h>
using namespace std;
const int MAXN=4e5+5;
inline void chkmax(int &x,const int &y) { x=y>x?y:x; }
int n,rt,a[MAXN],ls[MAXN],rs[MAXN],f[MAXN][3],stk[MAXN],ans;
char str[MAXN];
void dfs(int u) {
if(!u) return ;
int lc=ls[u],rc=rs[u];
dfs(lc),dfs(rc);
f[u][0]=f[lc][0],f[u][1]=f[rc][1],f[u][2]=f[lc][0]+f[rc][1];
chkmax(ans,f[lc][1]+f[rc][0]);
if(u==rt) return ;
if(str[u]!='R') {
chkmax(f[u][0],max(f[lc][1],f[rc][0])+1);
chkmax(f[u][2],max(f[lc][1]+f[rc][1],f[rc][2])+1);
chkmax(ans,max(f[lc][2],f[lc][0]+f[rc][0])+1);
}
if(str[u]!='L') {
chkmax(f[u][1],max(f[lc][1],f[rc][0])+1);
chkmax(f[u][2],max(f[lc][2],f[lc][0]+f[rc][0])+1);
chkmax(ans,max(f[lc][1]+f[rc][1],f[rc][2])+1);
}
}
void solve() {
scanf("%d",&n);
for(int i=1;i<=n;++i) scanf("%d",&a[i]),ls[i]=rs[i]=0;
scanf("%s",str+1);
int tp=0;
for(int i=1;i<=n;++i) {
while(tp&&a[stk[tp]]<a[i]) ls[i]=stk[tp--];
if(tp) rs[stk[tp]]=i;
stk[++tp]=i;
}
rt=stk[1];
for(int u=ls[rt];u;u=ls[u]) {
if(str[u]=='L') return puts("-1"),void();
str[u]='R';
}
for(int u=rs[rt];u;u=rs[u]) {
if(str[u]=='R') return puts("-1"),void();
str[u]='L';
}
ans=0,dfs(rt);
printf("%d\n",ans);
}
signed main() {
int T; scanf("%d",&T);
while(T--) solve();
return 0;
}
Round #36 - 2025.12.10
A. [CF1991G] Grid Reset
题目大意
给定
矩阵, 次操作要求填入 ,不能重叠,如果某行或某列被填满,则该行或该列会被清空,构造方案。 数据范围:
。
思路分析
考虑贪心,竖块只填第一行,横块只填第一列,如果能产生消行就选择该位置,否则任选一个位置。
把棋盘分成左上角的
很显然任何时候每个子区域都是完整的若干行或若干列。
满足该条件时一定有解:假设一个横块填不了,那么左上角和左下角都必须是若干列,但在右下角是若干列的时候,一定是在左上角放了竖块引发的消行,从而左上区域不存在竖块,可以放横块。
时间复杂度
代码呈现
#include<bits/stdc++.h>
using namespace std;
const int MAXN=105;
int n,m,k,q;
string str;
bool a[MAXN][MAXN],ti[MAXN],tj[MAXN];
void fls() {
fill(ti+1,ti+n+1,1),fill(tj+1,tj+m+1,1);
for(int i=1;i<=n;++i) for(int j=1;j<=m;++j) if(!a[i][j]) ti[i]=tj[j]=0;
for(int i=1;i<=n;++i) for(int j=1;j<=m;++j) if(ti[i]||tj[j]) a[i][j]=0;
}
void solve() {
cin>>n>>m>>k>>q>>str;
memset(a,0,sizeof(a));
for(char op:str) {
if(op=='H') {
int x=0;
for(int i=1;i<=n;++i) {
bool ok=1;
for(int j=1;j<=k;++j) ok&=!a[i][j];
if(ok) x=i;
for(int j=k+1;j<=m;++j) ok&=a[i][j];
if(ok) break;
}
cout<<x<<" "<<1<<"\n";
for(int j=1;j<=k;++j) a[x][j]=1;
} else {
int y=0;
for(int j=1;j<=m;++j) {
bool ok=1;
for(int i=1;i<=k;++i) ok&=!a[i][j];
if(ok) y=j;
for(int i=k+1;i<=n;++i) ok&=a[i][j];
if(ok) break;
}
cout<<1<<" "<<y<<"\n";
for(int i=1;i<=k;++i) a[i][y]=1;
}
fls();
}
}
signed main() {
ios::sync_with_stdio(false);
int T; cin>>T;
while(T--) solve();
return 0;
}
B. [CF1993E] Xor-Grid Problem
题目大意
给定
矩阵,每次操作选择一行或一列,把该行的所有值替换为所在列的异或和,或把该列的所有值替换为所在行的异或和,最小化所有相邻元素的差的绝对值之和。 数据范围:
。
思路分析
把某个元素替换为整行异或和,等价于交换整行异或和和当前元素。
那么添加一个虚拟行和虚拟列,虚拟列的元素就是所在行的异或和,虚拟行的元素就是所在列的异或和。
那么操作就是交换虚拟行和某一行,或交换虚拟列和某一列。
看成删去一行一列后任意重排行列,重排时行列贡献独立,分别状压 dp 求出最小权哈密顿路。
注意到考虑行之间的贡献只需要枚举删除了哪一列,删除每一行的贡献都能直接计算。
设
代码呈现
#include<bits/stdc++.h>
using namespace std;
const int inf=1e9;
int n,m,a[16][16],w[16][16],dp[1<<16][16],res[16][16];
inline void chkmin(int &x,const int &y) { x=y<x?y:x; }
void DP(int q) {
memset(dp,0x3f,sizeof(dp));
for(int i=0;i<q;++i) dp[1<<i][i]=0;
for(int s=0;s<(1<<q);++s) {
for(int i=0;i<q;++i) if(s>>i&1) {
for(int j=0;j<q;++j) if(!(s>>j&1)) {
chkmin(dp[s|(1<<j)][j],dp[s][i]+w[i][j]);
}
}
}
}
void solve() {
cin>>n>>m;
memset(a,0,sizeof(a));
memset(res,0,sizeof(res));
for(int i=1;i<=n;++i) for(int j=1;j<=m;++j) {
cin>>a[i][j],a[0][j]^=a[i][j],a[i][0]^=a[i][j],a[0][0]^=a[i][j];
}
for(int i=0;i<=n;++i) {
memset(w,0,sizeof(w));
for(int u=0;u<=m;++u) for(int v=u+1;v<=m;++v) {
for(int j=0;j<=n;++j) if(i!=j) w[u][v]+=abs(a[j][u]-a[j][v]);
w[v][u]=w[u][v];
}
DP(m+1);
int U=(1<<(m+1))-1;
for(int j=0;j<=m;++j) {
int mn=inf;
for(int k=0;k<=m;++k) if(k!=j) chkmin(mn,dp[U-(1<<j)][k]);
res[i][j]+=mn;
}
}
for(int i=0;i<=m;++i) {
memset(w,0,sizeof(w));
for(int u=0;u<=n;++u) for(int v=u+1;v<=n;++v) {
for(int j=0;j<=m;++j) if(i!=j) w[u][v]+=abs(a[u][j]-a[v][j]);
w[v][u]=w[u][v];
}
DP(n+1);
int U=(1<<(n+1))-1;
for(int j=0;j<=n;++j) {
int mn=inf;
for(int k=0;k<=n;++k) if(k!=j) chkmin(mn,dp[U-(1<<j)][k]);
res[j][i]+=mn;
}
}
int ans=inf;
for(int i=0;i<=n;++i) for(int j=0;j<=m;++j) chkmin(ans,res[i][j]);
cout<<ans<<"\n";
}
signed main() {
ios::sync_with_stdio(false);
int T; cin>>T;
while(T--) solve();
return 0;
}
C. [CF1993F] Dyn-scripted Robot
题目大意
给定
矩形网格,以及一条长度为 的操作序列,一个点从 开始按操作序列向上下左右运动,当一个点超出左右边界,就翻转操作序列中的左右操作,当一个点超出上下边界,就翻转操作序列中的上下操作。 查询执行
次操作序列后会经过多少次原点。 数据范围:
。
思路分析
如果遇到边界后不翻转操作,那么这个点会在无穷大网格上运动,划分成若干
如果这个点当前位置和起点左右间隔奇数个区域,那么当前经过奇数条左右边界,同理如果这个点当前位置和终点上下间隔奇数个区域,那么当前经过奇数条上下边界。
因此每个点都唯一对应原始
那么对应
因此枚举操作序列的位置,相当于求有多少个
时间复杂度
代码呈现
#include<bits/stdc++.h>
#define ll long long
using namespace std;
ll exgcd(ll a,ll b,ll &x,ll &y) {
if(!b) return x=1,y=0,a;
ll g=exgcd(b,a%b,y,x);
return y-=a/b*x,g;
}
ll inv(ll a,ll p) {
ll x,y; exgcd(a,p,x,y);
return (x%p+p)%p;
}
const int MAXN=1e6+5;
int px[MAXN],py[MAXN];
char str[MAXN];
void solve() {
ll _,k,n,m,ans=0;
cin>>_>>k>>m>>n>>(str+1),n*=2,m*=2;
for(int i=1;i<=_;++i) {
px[i]=(px[i-1]+(str[i]=='D'?n-1:str[i]=='U'))%n;
py[i]=(py[i-1]+(str[i]=='L'?m-1:str[i]=='R'))%m;
}
ll dx=px[_],dy=py[_],gx=__gcd(dx,n),gy=__gcd(dy,m);
ll p=n/gx,q=m/gy,ix=inv(dx/=gx,p),iy=inv(dy/=gy,q);
ll u,v,d=exgcd(p,q,u,v),e=p/d*q;
for(int i=1;i<=_;++i) {
ll rx=(n-px[i])%n,ry=(m-py[i])%m;
if(rx%gx||ry%gy) continue;
rx=rx/gx*ix%p,ry=ry/gy*iy%q;
//k mod p = rx, k mod q = ry
if((ry-rx)%d) continue;
ll z=q/d,s=((ry-rx)/d*u%z+z)%z,k0=s*p+rx;
if(k0<k) ans+=(k-k0-1)/e+1;
}
cout<<ans<<"\n";
}
signed main() {
ios::sync_with_stdio(false);
int T; cin>>T;
while(T--) solve();
return 0;
}
*D. [CF1991H] Prime Split Game
题目大意
给定
个数 ,每次操作可以选择 ,然后删除 个数,再选择另外的 个数,把每个数写成两个质数之和,并把两个质数加入 。 两个人轮流操作,不能操作的人输,问谁必胜。
数据范围:
思路分析
考虑
如果
如果
如果
因此
回到原问题,从边界条件开始:如果
那么对于每个被拆分的数
那么后手可以选择
此时后手有必胜策略。
那么先手的目标就是把所有先手必胜的数删除掉。
很显然先手可以拆分
最后仅剩的情况是
此时先手不一定必败,如果存在一个数可以拆成两个先手必胜数,那么先手拆分之,就可以把同样的局面留给对手。
我们称这种数为“二阶必胜数”,二阶必胜数就是所有质数的先手必胜数的和。
如果不存在二阶必胜数,那么至少会给后手留一个先手必胜数,那么先手必败。
否则先手的目标变为删除所有二阶必胜数,同上,要求二阶必胜数的个数为
如果二阶必胜数有
很显然这是不行的,因为所有质必胜数都是奇数,所以二阶必胜数都是偶数,那么不可能拆出二阶必胜数。
所以二阶必胜数有
我们只要求出所有先手必胜数和二阶必胜数,这是一个卷积问题,可以 NTT 或者 std::bitset
,注意到我们求的是能否把
时间复杂度
代码呈现
#include<bits/stdc++.h>
using namespace std;
const int MAXN=2e5+5;
bitset <MAXN> isp,f,g,o,t; //f: win, g: double win
void solve() {
int n,cf=0,cg=0;
cin>>n;
for(int i=1,x;i<=n;++i) cin>>x,cf+=f[x],cg+=g[x];
if(!cf) cout<<"Bob\n";
else if(n%2==0||cf<n) cout<<"Alice\n";
else if(cg==0||cg==n) cout<<"Bob\n";
else cout<<"Alice\n";
}
signed main() {
const int n=2e5;
isp.set(),isp[0]=isp[1]=0;
for(int i=2;i<=n;++i) if(isp[i]) for(int j=2*i;j<=n;j+=i) isp[j]=0;
f[4]=1;
for(int i=3;i<=n;i+=2) {
if(isp[i-2]&!f[i-2]) f[i]=1;
if(isp[i]&&!f[i]) o[i]=1;
}
for(int i=3;i<=n;i+=2) if(o[i]) t=o,t<<=i,f|=t;
o.reset();
for(int i=3;i<=n;i+=2) if(f[i]&&isp[i]) o[i]=1;
for(int i=3;i<=n;i+=2) if(o[i]) t=o,t<<=i,g|=t;
ios::sync_with_stdio(false);
int T; cin>>T;
while(T--) solve();
return 0;
}
*E. [CF1991I] Grid Game
题目大意
给定
网格,你需要给每个格子赋 权值(不重复),然后和交互器进行如下游戏:
- 两人轮流选择网格中的一个格子(不可重复),交互器先手,除第一步外,所选的格子必须和某个选过的格子有公共边。
你需要合适的赋权并构造策略,使得你选出的格子上元素总和总是小于交互器选出的。
数据范围:
。
思路分析
先考虑
这是容易做到的,把
对于
一个自然的想法是在中间放一个较小值,在四周放较大值,此时先手不可能取到极小值,而后手在先手取走一个较大值后可以取走较小值。
但这样一个结构占用五个格子,因此到最后被迫取走较大值的人其实是你。
那么我们把这种结构放在网格边界,此时较小值周围只有三个格子,填较大值后,一定是交互器取较大值而你取走较小值。
注意交互器可以从第一步一个较小值开始,从而让你亏损一些贡献,因此我们要多构造几个这种结构才能确保获胜。
事实上构造四个这样的结构即可,权值分别为:
可以证明任何情况下,交互器至少会比你多
构造时只要把一个网格划分成四个靠着边界的 T 块和若干个
时间复杂度
代码呈现
在
#include<bits/stdc++.h>
using namespace std;
const int dx[]={0,0,1,-1},dy[]={1,-1,0,0};
struct poi { int x,y; };
vector <poi> g[105];
int n,m,a[15][15],b[15][15],rv,lv,q;
bool vis[15][15];
void I(int x,int y,char op='D') { //op=R/D
++q,b[x][y]=q,a[x][y]=rv--;
g[q].push_back({x,y});
(op=='R')?++y:++x;
b[x][y]=q,a[x][y]=rv--;
g[q].push_back({x,y});
}
void T(int x,int y) {
++q,b[x][y]=q,a[x][y]=lv--;
g[q].push_back({x,y});
for(int d:{0,1,2,3}) {
int i=x+dx[d],j=y+dy[d];
if(1<=i&&i<=n&&1<=j&&j<=m) {
b[i][j]=q,a[i][j]=rv--;
g[q].push_back({i,j});
}
}
}
void solve() {
cin>>n>>m;
if(n%2==1&&m%2==1) {
for(int i=1;i<=n;++i) {
for(int j=1;j<=m;++j) cout<<(i-1)*m+j<<" ";
cout<<endl;
}
set <array<int,2>> Q;
for(int o=1;o<=n*m;++o) {
int x,y;
if(o&1) cin>>x>>y;
else {
x=(*Q.begin())[0],y=(*Q.begin())[1];
cout<<x<<" "<<y<<endl;
}
vis[x][y]=true,Q.erase({x,y});
for(int d:{0,1,2,3}) {
int i=x+dx[d],j=y+dy[d];
if(1<=i&&i<=n&&1<=j&&j<=m&&!vis[i][j]) Q.insert({i,j});
}
}
return ;
}
lv=4,rv=n*m;
if(m==4) {
T(1,2),T(2,4);
if(n%2==0) {
T(n-1,1),T(n,3);
for(int i=2;i<=n-3;i+=2) I(i,1);
for(int i=3;i<=n-2;i+=2) I(i,2),I(i,3);
for(int i=4;i<=n-1;i+=2) I(i,4);
} else {
T(3,1),T(n,2);
for(int i=5;i<=n-1;i+=2) I(i,1);
for(int i=4;i<=n-2;i+=2) I(i,2);
for(int i=3;i<=n-1;i+=2) I(i,3);
for(int i=4;i<=n;i+=2) I(i,4);
}
} else if(m==5) {
T(2,1),T(1,3),T(2,5);
T(n,2),I(n-1,3,'R'),I(n,4,'R');
for(int i=4;i<=n-1;i+=2) I(i,1),I(i,5);
for(int i=3;i<=n-2;i+=2) I(i,2),I(i,3),I(i,4);
} else {
T(1,2),T(1,m-1),T(3,1),T(3,m);
if(m%2==1) {
I(1,4),I(2,3),I(2,5),I(3,4);
I(4,2,'R'),I(4,5,'R');
for(int i=5;i<=m-3;i+=2) I(1,i,'R');
for(int i=6;i<=m-2;i+=2) I(2,i,'R'),I(3,i,'R');
for(int i=7;i<=m-1;i+=2) I(4,i,'R');
} else {
for(int i=4;i<=m-3;i+=2) I(1,i,'R');
for(int i=3;i<=m-2;i+=2) I(2,i,'R'),I(3,i,'R');
for(int i=2;i<=m-1;i+=2) I(4,i,'R');
}
if(m%2==0) {
for(int i=5;i<=n;++i) for(int j=1;j<=m;j+=2) I(i,j,'R');
} else {
for(int i=5;i<=n;i+=2) for(int j=1;j<=m;++j) I(i,j);
}
}
for(int i=1;i<=n;++i) {
for(int j=1;j<=m;++j) cout<<a[i][j]<<" ";
cout<<endl;
}
for(int i=1;i<=q;++i) {
sort(g[i].begin(),g[i].end(),[&](auto u,auto v){ return a[u.x][u.y]<a[v.x][v.y]; });
}
for(int o=1,x,y;o<=n*m;++o) {
if(o&1) { cin>>x>>y,vis[x][y]=true; continue; }
else {
for(auto z:g[b[x][y]]) if(!vis[z.x][z.y]) {
vis[z.x][z.y]=true,cout<<z.x<<" "<<z.y<<endl;
break;
}
}
}
}
signed main() {
int T; cin>>T;
while(T--) {
solve();
memset(vis,0,sizeof(vis));
memset(a,0,sizeof(a));
memset(b,0,sizeof(b));
for(int i=1;i<=q;++i) g[i].clear();
lv=rv=q=0;
}
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具