2025 省选做题记录(三)
Round #41 - 2024.12.31
A. [CF1900F] Local Deletions
题目大意
定义数组
的权值为不断重复如下操作直到 时剩下的数:
- 仅保留
的元素。 - 仅保留
的元素。 给定
排列 , 次询问 的权值。 数据范围:
。
思路分析
题目中的操作不存在方便刻画的性质,因此想求
但我们发现每次操作后,
回到原题,我们求出原序列操作一轮后的结果,如果仅对
同理,操作第二轮,只可能改变
因此我们提取出当前序列的前后
时间复杂度
代码呈现
#include<bits/stdc++.h>
using namespace std;
typedef vector<int> vi;
const int MAXN=1e5+5;
int n,q,a[MAXN];
vi f[32];
void trs(const vi &u,vi &v,bool op,int ed=-1) { //0: local max
auto cmp=[&](int i,int j) {
int tp=(j>=(int)u.size()?ed:(j<0?-1:a[u[j]]));
return tp==-1||(op?a[u[i]]<tp:a[u[i]]>tp);
};
for(int i=0;i<(int)u.size();++i) if(cmp(i,i-1)&&cmp(i,i+1)) v.push_back(u[i]);
}
signed main() {
scanf("%d%d",&n,&q);
for(int i=1;i<=n;++i) scanf("%d",&a[i]),f[0].push_back(i);
for(int i=1;f[i-1].size()>1;++i) trs(f[i-1],f[i],i&1);
for(int l,r;q--;) {
scanf("%d%d",&l,&r);
vi L,R,o;
int i=1;
for(;;++i) {
auto il=lower_bound(f[i-1].begin(),f[i-1].end(),l);
auto ir=upper_bound(f[i-1].begin(),f[i-1].end(),r)-1;
if(ir-il<=3) {
for(auto it=il;it<=ir;++it) L.push_back(*it);
for(int j=(int)R.size()-1;~j;--j) L.push_back(R[j]);
break;
}
L.push_back(*il),R.push_back(*ir);
trs(L,o,i&1,a[*++il]),L.swap(o),o.clear();
trs(R,o,i&1,a[*--ir]),R.swap(o),o.clear();
l=*il,r=*ir;
}
for(;L.size()>1;++i) {
trs(L,o,i&1),L.swap(o),o.clear();
}
printf("%d\n",a[L[0]]);
}
return 0;
}
B. [CF1906C] Cursed Game
题目大意
交互器有一个
的 01 矩阵 ,你每次可以询问一个 的矩阵 ,返回 矩阵 满足 。 你需要构造一个
使得返回的 是全 矩阵,要求 组数据的交互次数 。 数据范围:
。
思路分析
首先如果
知道
如果
时间复杂度
代码呈现
#include<bits/stdc++.h>
using namespace std;
mt19937 rnd(time(0));
int n,a[45][45],b[3][3];
string op;
void solve() {
cin>>n;
if(n==3) {
while(true) {
cout<<(rnd()&1)<<(rnd()&1)<<(rnd()&1)<<endl;
cout<<(rnd()&1)<<(rnd()&1)<<(rnd()&1)<<endl;
cout<<(rnd()&1)<<(rnd()&1)<<(rnd()&1)<<endl;
cin>>op;
if(op=="CORRECT") return ;
int x; cin>>x;
}
return ;
}
for(int i=1;i<=n;++i) {
for(int j=1;j<=n;++j) cout<<(i==3&&j==3);
cout<<endl;
}
cin>>op;
if(op=="CORRECT") return ;
for(int i=1;i<=n-2;++i) for(int j=1;j<=n-2;++j) {
char w; cin>>w; a[i][j]=w-'0';
}
for(int i=1;i<=3;++i) for(int j=1;j<=3;++j) b[3-i][3-j]=a[i][j];
memset(a,0,sizeof(a));
int x=0,y=0;
for(int i:{0,1,2}) for(int j:{0,1,2}) if(b[i][j]) x=i,y=j;
for(int i=1;i<=n-2;++i) for(int j=1;j<=n-2;++j) {
int val=0;
for(int u:{0,1,2}) for(int v:{0,1,2}) val^=b[u][v]&a[i+u][j+v];
if(!val) a[i+x][j+y]^=1;
}
for(int i=1;i<=n;++i) {
for(int j=1;j<=n;++j) cout<<a[i][j];
cout<<endl;
}
cin>>op;
}
signed main() {
int _=333;
while(_--) solve();
return 0;
}
*C. [CF1895G] Two Characters, Two Colors
题目大意
给定长度为
的 01 串 ,每个位置保留有权值 ,删除有权值 ,最大化获得权值之和减去剩余元素的逆序对数。 数据范围:
。
思路分析
对于原序列的一对逆序对
如果
很显然每个点优先从
然后对于每个
很显然这是贪心问题,每个
时间复杂度
代码呈现
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=4e5+5;
mt19937 rnd(time(0));
struct Treap {
int ls[MAXN],rs[MAXN],pri[MAXN],siz[MAXN],tg[MAXN];
ll val[MAXN];
void adt(int p,int k) { if(p) val[p]+=k,tg[p]+=k; }
void psd(int p) { adt(ls[p],tg[p]),adt(rs[p],tg[p]),tg[p]=0; }
void psu(int p) { siz[p]=siz[ls[p]]+1+siz[rs[p]]; }
void spiv(int p,ll k,int &x,int &y) {
if(!p) return x=y=0,void();
psd(p);
if(val[p]<=k) x=p,spiv(rs[p],k,rs[x],y),psu(x);
else y=p,spiv(ls[p],k,x,ls[y]),psu(y);
}
void spiz(int p,int k,int &x,int &y) {
if(!p) return x=y=0,void();
psd(p);
if(siz[ls[p]]<k) x=p,spiz(rs[p],k-siz[ls[p]]-1,rs[x],y),psu(x);
else y=p,spiz(ls[p],k,x,ls[y]),psu(y);
}
int merge(int x,int y) {
if(!x||!y) return x|y;
psd(x),psd(y);
if(pri[x]<pri[y]) return rs[x]=merge(rs[x],y),psu(x),x;
else return ls[y]=merge(x,ls[y]),psu(y),y;
}
ll mx(int x) {
for(;rs[x];x=rs[x]) psd(x);
return val[x];
}
ll mn(int x) {
for(;ls[x];x=ls[x]) psd(x);
return val[x];
}
} T;
char op[MAXN];
ll a[MAXN],b[MAXN];
void solve() {
int n,rt=0;
cin>>n;
ll ans=0,all=0;
for(int i=1;i<=n;++i) cin>>op[i];
for(int i=1;i<=n;++i) cin>>a[i];
for(int i=1;i<=n;++i) cin>>b[i];
for(int i=1,x,y;i<=n;++i) {
ans+=min(a[i],b[i]),all+=a[i]+b[i];
if(a[i]<=b[i]) continue;
if(op[i]=='1') {
ll k=a[i]-b[i];
T.val[i]=k,T.pri[i]=rnd(),T.siz[i]=1;
T.spiv(rt,k,x,y);
rt=T.merge(T.merge(x,i),y);
}
if(op[i]=='0'&&rt) {
ll k=a[i]-b[i];
if(T.siz[rt]<=k) ans+=T.siz[rt],T.adt(rt,-1);
else {
T.spiz(rt,T.siz[rt]-k,x,y);
T.adt(y,-1),ans+=k;
ll vl=T.mx(x),vr=T.mn(y);
if(vl>vr) {
int xl,xr,yl,yr;
T.spiv(x,vl-1,xl,xr);
T.spiv(y,vr,yl,yr);
rt=T.merge(T.merge(xl,yl),T.merge(xr,yr));
} else rt=T.merge(x,y);
}
if(!T.mn(rt)) T.spiv(rt,0,x,y),rt=y;
}
}
cout<<all-ans<<"\n";
for(int i=1;i<=n;++i) {
T.ls[i]=T.rs[i]=T.siz[i]=T.pri[i]=T.tg[i]=T.val[i]=0;
}
}
signed main() {
ios::sync_with_stdio(false);
cin.tie(0),cout.tie(0);
int _; cin>>_;
while(_--) solve();
return 0;
}
*D. [CF1896G] Pepe Racing
题目大意
交互器中有
个不同元素,每次可以询问 个不同元素中的最大值,在 次询问内找到前 大元素及其顺序。 数据范围:
。
思路分析
很自然的想法就是分块,分成
每次弹出最大值之后,重新求出该块最大值,但是此时块的大小
此时询问次数
那么我们找到最后的
此时我们还剩
那么每次询问
恰好需要
时间复杂度
代码呈现
#include<bits/stdc++.h>
using namespace std;
vector <int> a[25];
int n,mx[25];
int find(vector<int>&v,int x) { return find(v.begin(),v.end(),x)-v.begin(); }
int qry(vector<int>&v) {
cout<<"? "; for(int i:v) cout<<i<<" "; cout<<endl;
int r; cin>>r; return r;
}
void solve() {
cin>>n;
for(int i=1;i<=n;++i) {
for(int j=1;j<=n;++j) a[i].push_back((i-1)*n+j);
mx[i]=qry(a[i]),swap(a[i][0],a[i][find(a[i],mx[i])]);
}
vector <int> ans;
for(int o=n*n;o>=2*n;--o) {
vector <int> b;
for(int i=1;i<=n;++i) b.push_back(mx[i]);
ans.push_back(qry(b));
for(int i=1;i<=n;++i) if(mx[i]==ans.back()) {
a[i].erase(a[i].begin());
for(int j=1;j<=n&&(int)a[i].size()<n;++j) if(i!=j) {
while((int)a[i].size()<n&&(int)a[j].size()>1) {
a[i].push_back(a[j].back()),a[j].pop_back();
}
}
mx[i]=qry(a[i]),swap(a[i][0],a[i][find(a[i],mx[i])]);
}
}
vector <int> b,c;
for(int i=1;i<=n;++i) {
b.push_back(mx[i]);
for(int j=1;j<(int)a[i].size();++j) c.push_back(a[i][j]);
}
for(int o=n;o>1;--o) {
ans.push_back(qry(b));
b.erase(find(b.begin(),b.end(),ans.back()));
b.push_back(c.back()),c.pop_back();
}
ans.push_back(b[0]);
cout<<"! "; for(int i:ans) cout<<i<<" "; cout<<endl;
for(int i=1;i<=n;++i) a[i].clear(),mx[i]=0;
}
signed main() {
int _; cin>>_;
while(_--) solve();
return 0;
}
E. [CF1893E] Cacti Symphony
题目大意
给定
个点 条边的点仙人掌,在每条边中间插入若干个点,给每个点和边赋权 。 使得每条边的端点异或和
且不等于边权,每个点的邻边异或和 且不等于点权,求方案数。 数据范围:
。
思路分析
先观察边
然后考虑点
如果
因此我们只需要
把每条边定向到
先考虑边权,这是经典问题,只要
赋权的方案数可以把环看成点,从上到下填颜色,每条边会让方案数
对于一个大小为
Tarjan 找出每个环即可。
时间复杂度
代码呈现
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=5e5+5,MOD=998244353,i3=(MOD+1)/3;
ll ksm(ll a,ll b) { ll s=1; for(b%=MOD-1;b;a=a*a%MOD,b>>=1) if(b&1) s=s*a%MOD; return s; }
struct Edge { int v,i,w; };
vector <Edge> G[MAXN];
int dfn[MAXN],low[MAXN],dcnt;
bool ins[MAXN],br[MAXN*2],vis[MAXN];
ll bc,vc,cc,sz; //bridge, ver, cycle, cyc-siz
void tarjan(int u,int fz) {
dfn[u]=low[u]=++dcnt;
for(auto e:G[u]) if(e.v^fz) {
if(!dfn[e.v]) {
tarjan(e.v,u),low[u]=min(low[u],low[e.v]);
if(low[e.v]>dfn[u]) br[e.i]=true,bc+=e.w+1,vc+=e.w;
} else low[u]=min(low[u],dfn[e.v]);
}
}
void dfs(int u) {
if(vis[u]) return ; vis[u]=true;
for(auto e:G[u]) {
if(!br[e.i]) sz+=e.w+1,br[e.i]=true,dfs(e.v);
}
}
signed main() {
ios::sync_with_stdio(false);
cin.tie(0),cout.tie(0);
int n,m;
cin>>n>>m;
if(n%2!=m%2) return cout<<"0\n",0;
for(int i=1,u,v,w;i<=m;++i) {
cin>>u>>v>>w;
G[u].push_back({v,i,w});
G[v].push_back({u,i,w});
}
ll ans=1;
tarjan(1,0);
for(int i=1;i<=n;++i) if(!vis[i]) {
sz=0,dfs(i);
if(!sz) { ++vc; continue; }
ans=ans*(ksm(2,sz)+(sz&1?MOD-2:2))%MOD,++cc;
}
ans=ans*ksm(i3,bc)%MOD*ksm(3,vc)%MOD*ksm(2,bc+cc)%MOD;
cout<<ans<<"\n";
return 0;
}
Round #42 - 2025.1.2
A. [CF1874D] Jellyfish and Miku
题目大意
给定长度为
的链,给每条边赋一个权重 ,每个点会按权重随机一条边移动,要求 ,最小化 的期望移动步数。 数据范围:
。
思路分析
根据期望线性性,计算
因此直接 dp,
但根据贪心思想,不难证明
时间复杂度
代码呈现
#include<bits/stdc++.h>
#define ld long double
using namespace std;
const int MAXN=3005;
const ld inf=1e18;
ld f[MAXN],g[MAXN];
signed main() {
int n,m;
scanf("%d%d",&n,&m);
for(int j=0;j<=m;++j) f[j]=g[j]=inf;
f[0]=0;
for(int i=1;i<=n;++i) {
for(int s=0;s<=m;++s) {
for(int j=1;s+j<=m&&s+(n-i)*j<=m;++j) {
g[s+j]=min(g[s+j],f[s]+(ld)s/j);
}
}
for(int j=0;j<=m;++j) f[j]=g[j],g[j]=inf;
}
ld ans=inf;
for(int j=0;j<=m;++j) ans=min(ans,f[j]);
printf("%.20Lf\n",ans*2+n);
return 0;
}
B. [CF1870F] Lazy Numbers
题目大意
给定
,求 在 进制下有多少个数值等于其字典序排名。 数据范围:
。
思路分析
求正整数的字典序排名可以直接建 Trie,那么数值等于其 bfs 序,字典序排名等于其 dfs 序。
对每层的节点分别考虑,同层节点 bfs 序每次
那么我们只要支持快速计算每个数的字典序排名,逐层计数即可。
时间复杂度
代码呈现
#include<bits/stdc++.h>
#define ll long long
#define LL __int128
using namespace std;
ll n,m,pw[65],len;
ll rk(int k,ll x) {
ll s=0,y=x;
for(int i=k;~i;--i) s+=y-pw[i]+1,y/=m;
if(k==len) return s;
for(int i=k+1;i<len;++i) s+=(x-pw[k])*pw[i-k];
s+=min((LL)n+1,(LL)x*pw[len-k])-pw[len];
return s;
}
void solve() {
scanf("%lld%lld",&n,&m),len=0;
for(pw[0]=1;(LL)pw[len]*m<=n;++len) pw[len+1]=pw[len]*m;
ll ans=0;
for(int i=0;i<=len;++i) {
ll L=pw[i],R=(i==len?n:pw[i+1]-1);
auto find=[&](ll d) {
ll l=L,r=R,p=l-1;
while(l<=r) {
ll mid=(l+r)>>1;
if(rk(i,mid)-mid<=d) p=mid,l=mid+1;
else r=mid-1;
}
return p;
};
ans+=find(0)-find(-1);
}
printf("%lld\n",ans);
}
signed main() {
int T; scanf("%d",&T);
while(T--) solve();
return 0;
}
C. [CF1906I] Contingency Plan 2
题目大意
给定
个点 条边的弱联通 DAG,加入最少的边使得该图拓扑序唯一。 数据范围:
。
思路分析
可以证明拓扑序唯一当且仅当最长链长度为
那么我们求出该图的最小链覆盖,然后把所有链缩点,按拓扑序连起来即可。
时间复杂度
代码呈现
#include<bits/stdc++.h>
using namespace std;
namespace F {
const int MAXV=2e5+5,MAXE=6e5+5,inf=1e9;
struct Edge {
int v,f,lst;
} G[MAXE];
int S,T,tot=1,hd[MAXV],cur[MAXV],dep[MAXV];
void init() { tot=1,memset(hd,0,sizeof(hd)); }
void adde(int u,int v,int w) { G[++tot]={v,w,hd[u]},hd[u]=tot; }
int link(int u,int v,int w) { adde(u,v,w),adde(v,u,0); return tot; }
bool BFS() {
memcpy(cur,hd,sizeof(cur)),memset(dep,-1,sizeof(dep));
queue <int> Q;
Q.push(S),dep[S]=0;
while(!Q.empty()) {
int u=Q.front(); Q.pop();
for(int i=hd[u];i;i=G[i].lst) if(G[i].f&&dep[G[i].v]==-1) {
dep[G[i].v]=dep[u]+1,Q.push(G[i].v);
}
}
return ~dep[T];
}
int dfs(int u,int f) {
if(u==T) return f;
int r=f;
for(int i=cur[u];i;i=G[i].lst) {
int v=G[cur[u]=i].v;
if(G[i].f&&dep[v]==dep[u]+1) {
int g=dfs(v,min(r,G[i].f));
if(!g) dep[v]=-1;
G[i].f-=g,G[i^1].f+=g,r-=g;
}
if(!r) return f;
}
return f-r;
}
int Dinic() {
int f=0;
while(BFS()) f+=dfs(S,inf);
return f;
}
}
const int MAXN=1e5+5;
vector <int> G[MAXN];
int st[MAXN],ed[MAXN],id[MAXN],nxt[MAXN],ty[MAXN],deg[MAXN];
bool vis[MAXN];
signed main() {
int n;
scanf("%d",&n);
int s=F::S=2*n+1,t=F::T=2*n+2;
for(int i=1;i<=n;++i) F::link(s,i,1),F::link(i+n,t,1);
for(int i=1;i<n;++i) {
scanf("%d%d",&st[i],&ed[i]);
id[i]=F::link(st[i],ed[i]+n,1);
}
printf("%d\n",n-1-F::Dinic());
for(int i=1;i<n;++i) if(F::G[id[i]].f) nxt[st[i]]=ed[i],vis[ed[i]]=true;
for(int i=1;i<=n;++i) if(!vis[i]) for(int j=i;j;j=nxt[j]) ty[j]=i;
for(int i=1;i<n;++i) if(ty[st[i]]!=ty[ed[i]]) {
G[ty[st[i]]].push_back(ty[ed[i]]),++deg[ty[ed[i]]];
}
queue <int> Q;
for(int i=1;i<=n;++i) if(!deg[i]&&!vis[i]) Q.push(i);
for(int x=0;Q.size();) {
int u=Q.front(); Q.pop();
if(x) printf("%d %d\n",x,u);
for(x=u;nxt[x];x=nxt[x]);
for(int v:G[u]) if(!--deg[v]) Q.push(v);
}
return 0;
}
*D. [CF1874F] Jellyfish and OEIS
题目大意
给定
,求有多少 排列 ,使得对于所有 的 ,都有 。 数据范围:
。
思路分析
首先这个条件难以刻画,因此需要容斥处理。
我们钦定若干个区间
但这些区间的关系过于复杂,但我们注意到如果
所以我们只要考虑钦定的区间集合两两无交或包含的情况。
此时所有的区间显然构成树状结构,一个节点的方案数就是所有儿子的方案数乘积,再乘上未被儿子覆盖的点的阶乘,表示这些点无限制,可以在该节点对应的区间内重排。
因此按这个结构 dp,
时间复杂度
代码呈现
#include<bits/stdc++.h>
using namespace std;
const int MAXN=205,MOD=1e9+7;
int n,m[MAXN],fac[MAXN],f[MAXN][MAXN],g[MAXN][MAXN][MAXN];
signed main() {
scanf("%d",&n);
for(int i=fac[0]=1;i<=n;++i) scanf("%d",&m[i]),fac[i]=1ll*i*fac[i-1]%MOD;
for(int i=0;i<=n;++i) g[i+1][i][0]=1;
for(int l=n;l>=1;--l) for(int r=l;r<=n;++r) {
for(int x=1;x<=r-l+1;++x) g[l][r][x]=g[l][r-1][x-1];
for(int x=0;x<r-l+1;++x) {
int &z=g[l][r][x];
for(int i=(x?l+x-1:l);i<r;++i) z=(z+1ll*g[l][i][x]*f[i+1][r])%MOD;
}
if(r<=m[l]) {
for(int x=0;x<=r-l+1;++x) f[l][r]=(f[l][r]+1ll*(MOD-fac[x])*g[l][r][x])%MOD;
}
g[l][r][0]=(g[l][r][0]+f[l][r])%MOD;
}
int ans=0;
for(int x=0;x<=n;++x) ans=(ans+1ll*fac[x]*g[1][n][x])%MOD;
printf("%d\n",ans);
return 0;
}
*E. [CF1874G] Jellyfish and Inscryption
题目大意
给定
个点 条边的 DAG,从 走到 ,每个点上可能有如下的事件之一:
- A:获得一个二元组
。 - B:选择一个二元组
变成 。 - C:选择一个二元组
变成 。 - D:获得
的权值。 到达
之后你可以把一个 变成 ,然后每个二元组 会产生 的权值,最大化你获得的权值。 数据范围:
。
思路分析
先解决
最特殊的操作很显然是
所以
因此我们只要考虑
如果没有 B 操作,那么每个 C 操作肯定都会给前缀最大的
如果有 B 操作,那么直接来看难以确定 C 操作,但如果我们已经知道最终的每个
所以我们知道,所有 C 操作的决策点
所以可以得到一个朴素的 dp:
此时状态数是
我们注意到如果最终存在某个
那么此时
所以我们知道
此时我们尝试不记录
我们发现
那么我们在状态里要记录所有
由于刚刚的贪心结论,操作 B 的位置单调递增,因此每个
那么 dp 的时候记录
转移是平凡的,状态数
这个 dp 放到图上转移不变,只不过有多个后继,复杂度
最后我们还要从
如果不存在 A 类点,还要求 D 类点构成的最长路。
时间复杂度
代码呈现
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=205,V=200,inf=1e9;
vector <int> G[MAXN],rG[MAXN];
int n,m,op[MAXN],a[MAXN],b[MAXN];
inline void upd(ll &x,const ll &y) { x=y>x?y:x; }
void solve(auto f) {
//f[u,x,y]: 1->u, max a'=x, a'-a=y
f[1][0][0]=0;
for(int u=1;u<=n;++u) for(int v:G[u]) {
if(op[v]==0) {
for(int x=0;x<=V;++x) for(int y=0;y<=x;++y) {
upd(f[v][x][y],f[u][x][y]);
}
}
if(op[v]==1) {
for(int x=0;x<=V;++x) {
for(int y=0;y<=x;++y) {
upd(f[v][max(x,a[v])][y],f[u][x][y]+a[v]*b[v]);
}
for(int y=a[v];y<=V;++y) {
upd(f[v][max(x,y)][y-a[v]],f[u][x][0]+y*b[v]);
}
}
}
if(op[v]==2) {
for(int x=0;x<=V;++x) for(int y=0;y<=x;++y) {
upd(f[v][x][y],f[u][x][y]);
upd(f[v][x][max(0,y-a[v])],f[u][x][y]);
}
}
if(op[v]==3) {
for(int x=0;x<=V;++x) for(int y=0;y<=x;++y) {
upd(f[v][x][y],f[u][x][y]+x*a[v]);
}
}
if(op[v]==4) {
for(int x=0;x<=V;++x) for(int y=0;y<=x;++y) {
upd(f[v][x][y],f[u][x][y]+a[v]);
}
}
}
}
ll dp[2][MAXN][V+5][V+5];
array<ll,2> h[MAXN][MAXN*V+5];
//sum a, {sum b, sum other}
void init() {
h[n][0]={0,0};
for(int u=n;u>=1;--u) for(int v:rG[u]) {
if(op[v]==0) {
for(int x=0;x<=n*V;++x) h[v][x]=max(h[v][x],h[u][x]);
}
if(op[v]==1) {
for(int x=0;x<=n*V;++x) h[v][x]=max(h[v][x],{h[u][x][0],h[u][x][1]+a[v]*b[v]});
}
if(op[v]==2) {
for(int x=0;x<=n*V;++x) {
h[v][x]=max(h[v][x],h[u][x]);
if(x>=a[v]) h[v][x]=max(h[v][x],h[u][x-a[v]]);
}
}
if(op[v]==3) {
for(int x=0;x<=n*V;++x) h[v][x]=max(h[v][x],{h[u][x][0]+a[v],h[u][x][1]});
}
if(op[v]==4) {
for(int x=0;x<=n*V;++x) h[v][x]=max(h[v][x],{h[u][x][0],h[u][x][1]+a[v]});
}
}
}
ll dis[MAXN];
signed main() {
scanf("%d%d",&n,&m);
for(int i=1;i<=n;++i) {
scanf("%d",&op[i]);
if(op[i]==1) scanf("%d%d",&a[i],&b[i]);
else if(op[i]) scanf("%d",&a[i]);
}
for(int i=1,u,v;i<=m;++i) {
scanf("%d%d",&u,&v);
G[u].push_back(v),rG[v].push_back(u);
}
memset(dp,-0x3f,sizeof(dp));
memset(h,-0x3f,sizeof(h));
init(),solve(dp[0]);
for(int i=1;i<=n;++i) {
if(op[i]==1) swap(a[i],b[i]);
else if(op[i]==2||op[i]==3) op[i]^=1;
}
solve(dp[1]);
for(int i=1;i<=n;++i) {
if(op[i]==1) swap(a[i],b[i]);
else if(op[i]==2||op[i]==3) op[i]^=1;
}
ll ans=0;
for(int u=1;u<=n;++u) if(op[u]==1) {
ll Z=0;
for(int i=0;i<=n*V;++i) if(h[u][i][0]>=0) Z=max(Z,1ll*(a[u]+i)*(b[u]+h[u][i][0])*inf+h[u][i][1]-a[u]*b[u]);
for(int x=0;x<=V;++x) ans=max(ans,max(dp[0][u][x][0],dp[1][u][x][0])-a[u]*b[u]+Z);
}
memset(dis,-0x3f,sizeof(dis)),dis[1]=0;
for(int u=1;u<=n;++u) for(int v:G[u]) dis[v]=max(dis[v],dis[u]+(op[v]==4?a[v]:0));
ans=max(ans,dis[n]);
printf("%lld\n",ans);
return 0;
}
*F. [CF1868E] Min-Sum-Max
题目大意
给定
,将序列分成 段,每段元素和为 ,要求 满足 ,最大化 。 数据范围:
。
思路分析
求出前缀和数组
尝试判定:求出
不妨设
说明
根据归纳,我们一定能找到满足
我们对
注意到
那么找到这样的
那么我们可以划分子结构并 dp:
暴力转移复杂度
观察转移的形式:我们枚举
注意划分出的新状态
观察
那么
可以发现这部分已经封闭,可以完成转移,翻转
时间复杂度
代码呈现
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=305,inf=1e9;
int n,m,pre[MAXN],suf[MAXN];
ll a[MAXN],w[MAXN];
void upd(int &x,const int &y) { x=y>x?y:x; }
void DP(auto f,auto g) {
//f/g[l,r,x]: dp[l,r,a[l],x] / dp[l,r,x,a[l]]
for(int l=0;l<=n;++l) for(int r=0;r<=n;++r) for(int x=1;x<=m;++x) {
f[l][r][x]=(l==r&&a[l]<=x?1:-inf);
g[l][r][x]=(l==r&&x<=a[l]?1:-inf);
}
for(int l=n;l>=0;--l) {
for(int i=0,v=-1;i<=n;++i) pre[i]=v,v=(a[i]==a[l]?i:v);
for(int i=n,v=n+1;i>=0;--i) suf[i]=v,v=(a[i]==a[l]?i:v);
for(int r=l+1;r<=n;++r) {
for(int i=l;i<=r;++i) {
if(a[i]>=a[l]) {
if(l<=pre[i]) upd(f[l][r][a[i]],f[l][pre[i]][a[i]]+g[i][r][a[l]]);
if(suf[i]<=r) upd(f[l][r][a[i]],f[l][i][a[i]]+f[suf[i]][r][a[i]]);
}
if(a[i]<=a[l]) {
if(l<=pre[i]) upd(g[l][r][a[i]],g[l][pre[i]][a[i]]+f[i][r][a[l]]);
if(suf[i]<=r) upd(g[l][r][a[i]],g[l][i][a[i]]+g[suf[i]][r][a[i]]);
}
}
for(int x=2;x<=m;++x) upd(f[l][r][x],f[l][r][x-1]);
for(int x=m;x>=2;--x) upd(g[l][r][x-1],g[l][r][x]);
}
}
}
int dp[2][2][MAXN][MAXN][MAXN];
void solve() {
scanf("%d",&n),w[1]=a[0]=0,m=1;
for(int i=1;i<=n;++i) scanf("%lld",&a[i]),a[i]+=a[i-1],w[++m]=a[i];
sort(w+1,w+m+1),m=unique(w+1,w+m+1)-w-1;
for(int i=0;i<=n;++i) a[i]=lower_bound(w+1,w+m+1,a[i])-w;
DP(dp[0][0],dp[0][1]);
reverse(a,a+n+1);
DP(dp[1][0],dp[1][1]);
reverse(a,a+n+1);
int ans=1;
for(int x=0;x<=n;++x) for(int y=x+1;y<=n;++y) {
upd(ans,dp[1][a[x]>a[y]][n-x][n][a[y]]+dp[0][a[x]<=a[y]][y][n][a[x]]-1);
}
printf("%d\n",ans);
}
signed main() {
int T; scanf("%d",&T);
while(T--) solve();
return 0;
}
Round #43 - 2025.1.8
A. [CF2048G] Kevin and Matrices
题目大意
给定
矩阵,填入 使得每行最大值的最小值小于等于每列最小值的最大值。 数据范围:
。
思路分析
可以反面考虑,统计有多少矩阵每行最大值
然后接着容斥,钦定若干格子
因此当我们钦定的格子包含
直接枚举
此时答案为:
把
直接计算即可。
时间复杂度
代码呈现
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=1e6+5,MOD=998244353;
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 inv[MAXN];
void solve() {
ll n,m,v;
scanf("%lld%lld%lld",&n,&m,&v);
ll c=1,s=0;
for(int i=1;i<=n;++i) {
c=c*(n-i+1)%MOD*inv[i]%MOD;
for(int k=1;k<=v;++k) {
ll b=(ksm(k,i)*ksm(v,n-i)+MOD-ksm(v-k+1,n-i))%MOD;
b=(ksm(b,m)-ksm(k,i*m)*ksm(v,(n-i)*m))%MOD;
s=(s+(i&1?MOD-c:c)*(b+MOD))%MOD;
}
}
printf("%lld\n",s);
}
signed main() {
inv[1]=1;
for(int i=2;i<MAXN;++i) inv[i]=inv[MOD%i]*(MOD-MOD/i)%MOD;
int _;
scanf("%d",&_);
while(_--) solve();
return 0;
}
*B. [CF2048H] Kevin and Strange Operation
题目大意
给定长度为
的 01 串 ,每次操作可以选定一个 ,然后把 替换成 ,求能得到多少本质不同字符串。 数据范围:
。
思路分析
可以发现最终的每个字符都对应原串一段区间的最大值,不妨设新串每个字符对应
观察一次操作后每个
仅观察
那么在
翻转原串,此时
那么可以 dp:
如果
需要一个支持整体平移区间求和的数据结构,直接树状数组维护。
时间复杂度
代码呈现
#include<bits/stdc++.h>
using namespace std;
const int MAXN=1e6+5,MOD=998244353;
char s[MAXN];
int n,nxt[MAXN];
struct FenwickTree {
int dx,tr[MAXN*2],s;
void init() { fill(tr,tr+2*n+1,0); }
void add(int x,int v) { for(x+=dx;x<=2*n;x+=x&-x) tr[x]=(tr[x]+v)%MOD; }
int qry(int x) { for(s=0,x+=dx;x;x&=x-1) s=(s+tr[x])%MOD; return s; }
} F;
void solve() {
cin>>(s+1),F.dx=n=strlen(s+1);
reverse(s+1,s+n+1);
F.add(0,1);
int ans=0;
nxt[n+1]=n+1;
for(int i=n;i>=1;--i) nxt[i]=(s[i]=='1'?i:nxt[i+1]);
for(int i=1;i<=n;++i) {
--F.dx;
if(s[i]=='0'&&nxt[i]<=n) {
F.add(nxt[i],(F.qry(nxt[i]-1)+MOD-F.qry(i-1))%MOD);
}
ans=(ans+F.qry(n))%MOD;
}
cout<<ans<<"\n",F.init();
}
signed main() {
ios::sync_with_stdio(false);
cin.tie(0),cout.tie(0);
int _; cin>>_;
while(_--) solve();
return 0;
}
C. [CF2048I1] Kevin and Puzzle (Easy Version)
题目大意
给定长度为
的串 ,构造 使得 时 的颜色数 , 时 的颜色数 。 数据范围:
。
思路分析
从一些简易的情形入手,如果
因此可以分讨
:那么 ,且 中没有 ,因此可以看成给 直接 。 :不妨设 ,则 ,设 ,那么构造的时候 必须包含 的所有数。 :很显然 中没有 ,一种可能的构造是所有 。
分析上述条件,导出矛盾当且仅当
否则直接按照上述过程递归构造即可。
时间复杂度
代码呈现
#include<bits/stdc++.h>
using namespace std;
const int MAXN=2e5+5;
int n,a[MAXN],ok;
char s[MAXN];
int dfs(int l,int r,int d,int x) {
if(l>r) return 0;
if(l==r) return a[l]=x;
if(s[l]==s[r]) {
a[l]=x,a[r]=max(x,dfs(l+1,r-1,1,x+1))+1;
if(s[r]=='R') swap(a[l],a[r]);
return max(a[l],a[r]);
}
if(s[l]=='L') return a[l]=a[r]=x,max(x,dfs(l+1,r-1,d,x+1));
ok&=d!=1,fill(a+l,a+r+1,x+1);
return x+1;
}
void solve() {
cin>>n,ok=1;
for(int i=1;i<=n;++i) cin>>s[i],a[i]=0;
dfs(1,n,-1,0);
if(!ok) cout<<"-1\n";
else for(int i=1;i<=n;++i) cout<<a[i]<<" \n"[i==n];
}
signed main() {
ios::sync_with_stdio(false);
cin.tie(0),cout.tie(0);
int _; cin>>_;
while(_--) solve();
return 0;
}
D. [CF2057F] Formation
题目大意
给定
,其是好的当且仅当所有 , 次询问 ,求出给所有 总共增加 的值,最大化 。 数据范围:
。
思路分析
很显然可以二分,求出
观察
所以
所以我们对于每个
因此我们先预处理出每个
但
时间复杂度
代码呈现
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=5e4+5,mx=2e9;
const ll inf=1e18;
ll a[MAXN],ans[MAXN];
struct Heap {
priority_queue <ll> qi,qo;
void ins(ll x) { qi.push(x); }
void ers(ll x) { qo.push(x); }
ll top() {
while(qi.size()&&qo.size()&&qi.top()==qo.top()) qi.pop(),qo.pop();
return qi.size()?qi.top():-inf;
}
} f[32];
ll qry(ll x) {
ll z=inf,s=0;
for(int i=1;i<=30;++i) s+=x,x=(x+1)/2,z=min(z,s-f[i].top());
return z;
}
void solve() {
int n,q;
cin>>n>>q;
for(int i=1;i<=n;++i) cin>>a[i];
vector <array<ll,3>> op;
for(int i=1;i<=n;++i) {
ll s=0,l=0;
for(int j=1;j<=30&&j<=i;++j) {
s+=a[i-j+1];
ll r=(i==j?inf:2ll*a[i-j]*((1<<j)-1)-s);
op.push_back({l,-j,s}),op.push_back({r,j,s}),l=r;
}
}
for(int i=1,k;i<=q;++i) cin>>k,op.push_back({k,0,i});
sort(op.begin(),op.end());
for(auto i:op) {
if(i[1]<0) f[-i[1]].ins(i[2]);
else if(i[1]>0) f[i[1]].ers(i[2]);
else {
ll l=0,r=mx,p=0;
while(l<=r) {
ll mid=(l+r)>>1;
if(qry(mid)<=i[0]) p=mid,l=mid+1;
else r=mid-1;
}
ans[i[2]]=p;
}
}
for(int i=1;i<=q;++i) cout<<ans[i]<<" \n"[i==q];
}
signed main() {
ios::sync_with_stdio(false);
cin.tie(0),cout.tie(0);
int _; cin>>_;
while(_--) solve();
return 0;
}
E. [CF2057G] Secret Message
题目大意
在
网格上给出若干个格子,选择其中的若干个,使得每个未被选择格子与某个选择的格子四联通。 给出一种选择格子数
的方案,其中 是格子个数, 是格子构成图形的周长。 数据范围:
。
思路分析
这种问题可以考虑鸽巢原理,即构造
当网格为无穷大网格时,我们要恰好选出
回到原问题,对于每个
此时还会剩下一些格子未被覆盖,那么这个格子一定在边界上,这条边界外的格子一定满足
此时每条边界只会在恰某个
时间复杂度
代码呈现
#include<bits/stdc++.h>
using namespace std;
const int MAXN=2e6+5;
string s[MAXN];
vector <int> id[MAXN];
int G[MAXN][7];
bool vis[MAXN];
int n,m,q;
void link(int u,int v) { G[u][++G[u][0]]=v,G[v][++G[v][0]]=u; }
void upd(vector<int>&f) {
memset(vis,0,q+1);
for(int i:f) for(int j=1;j<=G[i][0];++j) vis[G[i][j]]=true;
for(int i=1;i<=q;++i) if(!vis[i]) f.push_back(i);
}
vector <int> f[5];
void solve() {
cin>>n>>m,q=0;
for(int i=0;i<n;++i) {
cin>>s[i],id[i].resize(m,0);
for(int j=0;j<m;++j) if(s[i][j]=='#') {
id[i][j]=++q,G[q][G[q][0]=1]=q;
if(i>0&&s[i-1][j]=='#') link(q,id[i-1][j]);
if(j>0&&s[i][j-1]=='#') link(q,id[i][j-1]);
f[(i+2*j)%5].push_back(q);
}
}
int x=0;
for(int r:{0,1,2,3,4}) {
upd(f[r]);
if(f[r].size()<f[x].size()) x=r;
}
memset(vis,0,q+1);
for(int i:f[x]) vis[i]=true;
for(int i=0;i<n;++i) {
for(int j=0;j<m;++j) if(vis[id[i][j]]) s[i][j]='S';
cout<<s[i]<<"\n";
string().swap(s[i]),vector<int>().swap(id[i]);
}
for(int r:{0,1,2,3,4}) f[r].clear();
}
signed main() {
for(int r:{0,1,2,3,4}) f[r].reserve(MAXN);
ios::sync_with_stdio(false);
cin.tie(0),cout.tie(0);
int _; cin>>_;
while(_--) solve();
return 0;
}
*F. [CF2057H] Coffee Break
题目大意
给定
,一次对 的操作会令 ,对于每个 求出若干次操作后 能达到的最大值。
思路分析
根据贪心,我们肯定不可能操作
那么我们的操作实际上和
那么动态维护
考虑
找到
不断进行这个操作,发现我们可以进行
那么用一个栈动态维护当前剩下的所有
特殊处理
时间复杂度
代码呈现
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=1e6+5;
int n,st[MAXN];
ll a[MAXN],b[MAXN],f[MAXN];
void sol(bool op) {
for(int i=1,tp=0;i<=n;++i) {
ll z=0;
while(tp) {
int d=i-st[tp];
if(b[i]>d) b[i]-=d+1,z+=d,--tp;
else break;
}
if(!tp) {
ll c=b[i]/(i+1);
b[i]-=(i+1)*c,z+=i*c;
}
if(b[i]>1) {
z+=b[i]-1;
if(tp) st[tp]+=b[i]-1;
else st[++tp]=b[i]-1;
} else if(!b[i]) st[++tp]=i;
b[i+1]+=z,f[op?n-i:i+1]+=z;
}
}
void solve() {
cin>>n;
for(int i=1;i<=n;++i) cin>>a[i],f[i]=a[i];
copy(a+1,a+n+1,b+1);
sol(0);
copy(a+1,a+n+1,b+1);
reverse(b+1,b+n+1);
sol(1);
for(int i=1;i<=n;++i) cout<<f[i]<<" \n"[i==n];
}
signed main() {
ios::sync_with_stdio(false);
cin.tie(0),cout.tie(0);
int _; cin>>_;
while(_--) solve();
return 0;
}
Round #44 - 2025.1.9
*A. [CF2035G2] Go Learn!
题目大意
给定
个限制 ,表示在 序列上二分第一个 的位置会得到 ( 序列不一定要有序)。 删除尽可能少的限制使得存在一个
序列满足上述限制,并求这样的 个数。 数据范围:
,所有 两两不同, 两两不同。
思路分析
很显然建立线段树,那么每个限制对应线段树上一条到叶子的路径,对每个节点
对于两个限制
因此保留的限制必须满足
把这些不可能满足的限制删除,第一问的答案就是 LIS。
然后考虑第二问,把所有限制按
假设限制
注意到除了
因此我们动态维护每个
注意特殊处理
时间复杂度
代码呈现
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=3e5+5,MOD=998244353,inf=1e9;
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; }
struct info {
int x; ll w;
inline friend info operator +(const info &u,const info &v) {
return u.x^v.x?(u.x>v.x?u:v):info{u.x,(u.w+v.w)%MOD};
}
inline info operator -(const info &o) const { return {x,(w+MOD-o.w)%MOD}; }
inline info operator *(const ll &o) const { return {x,w*o%MOD}; }
} f[MAXN],s1[MAXN],s2[MAXN];
vector <int> L[MAXN],R[MAXN];
int n,m,a[MAXN],p[MAXN];
void solve() {
scanf("%d%d",&n,&m);
ll inv=ksm(n);
for(int i=1,x,y;i<=m;++i) {
scanf("%d%d",&x,&y);
if(x==1||y>1) a[x]=y,p[y]=x;
}
for(int i=1;i<=n;++i) {
f[i]=s1[i]=s2[i]={-inf,1};
for(int l=1,r=n;l<r;) {
int j=(l+r)>>1;
if(i<=j) {
r=j;
if(i!=j) R[i].push_back(j);
} else l=j+1,L[i].push_back(j);
}
reverse(L[i].begin(),L[i].end());
reverse(R[i].begin(),R[i].end());
}
info ans={0,1};
for(int i=1;i<=n;++i) if(p[i]) {
int x=p[i]; ll vl=1,wys=(i-1)*inv%MOD;
for(int j:L[x]) {
f[x]=f[x]+(s1[j]*i-s2[j])*vl*inv;
if(a[j]) f[x]=f[x]+f[j]*vl;
vl=vl*wys%MOD;
}
f[x]=f[x]+info{0,vl},++f[x].x;
vl=1,wys=(n-i+1)*inv%MOD;
for(int j:R[x]) {
s1[j]=s1[j]+f[x]*vl;
s2[j]=s2[j]+f[x]*vl*i;
vl=vl*wys%MOD;
}
ans=ans+f[x]*vl;
}
printf("%d %lld\n",m-ans.x,ans.w*ksm(n,n-ans.x)%MOD);
for(int i=1;i<=n;++i) L[i].clear(),R[i].clear(),a[i]=p[i]=0;
}
signed main() {
int _; scanf("%d",&_);
while(_--) solve();
return 0;
}
*B. [CF2035H] Peak Productivity Forces
题目大意
给定两个
阶排列 ,每次操作可以选择一个 ,然后把 分别向右循环移位一位,构造一组 次操作以内使得 的操作序列。 数据范围:
。
思路分析
很显然可以转成给
这种用循环移位还原排列的问题,可以维护一个
如果
否则我们先把
那么我们按照这个方式构造到
考虑如何复原这部分,保证
如果
用上述方式还原排列,由于我们要保证
然后我们需要一个数据结构支持快速维护上述操作,注意到每次操作实际上会把绝大部分元素向右循环移位,只会单点修改
时间复杂度
代码呈现
#include<bits/stdc++.h>
using namespace std;
const int MAXN=5e5+5;
int n,A[MAXN],B[MAXN],z[MAXN];
struct arr {
int tg,p[MAXN],a[MAXN*3];
void init() { tg=0,fill(p+1,p+n+1,0),fill(a+1,a+3*n+1,0); }
inline int operator [](int i) { return a[i+2*n-tg]; }
inline int operator ()(int x) { return p[x]+tg; }
inline void set(int i,int x) { p[x]=i-tg,a[i+2*n-tg]=x; }
} a;
void opr(int i) {
int l=(i>1?a[i-1]:0),x=a[i],r=(i<n?a[n]:0);
++a.tg,a.set(i,x),cout<<i<<" ";
if(l) a.set(1,l);
if(r) a.set(i+1,r);
}
void solve() {
cin>>n;
for(int i=1;i<=n;++i) cin>>A[i];
for(int i=1;i<=n;++i) cin>>B[i],z[B[i]]=i;
if(n==1) return cout<<"0\n\n",void();
if(n==2) return cout<<(A[1]==B[1]?"0\n\n":"-1\n"),void();
for(int i=1;i<=n;++i) a.set(i,z[A[i]]);
if(a(n)<n) cout<<2*n-1<<"\n",opr(a(n)+1);
else cout<<2*n<<"\n",opr(1),opr(3);
for(int x=n-1;x>=1;--x) {
int p=a(x);
if(p<n) opr(p+1);
else if(x==1) opr(1);
else opr(a(x-1)+1),opr(a(x)==n?1:a(x)+1),--x;
}
for(int i=1;i<n;opr(n),++i) if(i<n-1&&a[n-2]==a[n-1]+1) opr(n-1),++i;
cout<<"\n",a.init();
}
signed main() {
ios::sync_with_stdio(false);
cin.tie(0),cout.tie(0);
int _; cin>>_;
while(_--) solve();
return 0;
}
*C. [CF2039F2] Shohag Loves Counting
题目大意
给定
,求有多少值域 的序列 满足: 互不相同,其中 表示所有长度为 的子区间的最大值构成的集合。
组数据。 数据范围:
。
思路分析
首先区间最大值问题建出笛卡尔树,那么
由于
那么笛卡尔树一定是一条链,我们只关心链的权值序列,对应的序列很显然有
那么我们现在要计数值域
用
这个形式难以优化,用莫比乌斯反演展开:
用
此时枚举
那么我们得到一个
尝试正序枚举
每个
如果我们正序枚举
实际上这就是转置原理,或者说原 dp 中令
那么做转置后的 dp 就能预处理出所有
时间复杂度
代码呈现
#include<bits/stdc++.h>
using namespace std;
const int MAXN=1e6+5,MOD=998244353;
vector <int> pr;
bool isc[MAXN];
int mu[MAXN],f[MAXN],g[MAXN],s[MAXN],rs[MAXN];
int a[MAXN*20],l[MAXN],r[MAXN];
inline void add(int &x,const int &y) { x=(x+y>=MOD)?x+y-MOD:x+y; }
inline void sub(int &x,const int &y) { x=(x>=y)?x-y:x+MOD-y; }
signed main() {
const int n=1e6;
mu[1]=1;
for(int i=2;i<=n;++i) {
if(!isc[i]) mu[i]=-1,pr.push_back(i);
for(int p:pr) {
if(i*p>n) break;
isc[i*p]=true,mu[i*p]=-mu[i];
if(i%p==0) { mu[i*p]=0; break; }
}
}
for(int i=1;i<=n;++i) for(int j=i;j<=n;j+=i) ++r[j];
for(int i=1;i<=n;++i) l[i]=r[i]+=r[i-1];
for(int i=n;i>=1;--i) for(int j=i;j<=n;j+=i) a[--l[j]]=i;
s[1]=1;
int ans=0;
for(int x=1;x<=n;++x) {
for(int u=l[x],i;u<r[x];++u) {
i=a[u],g[i]=f[i];
for(int j=l[i];j<r[i];++j) add(g[i],s[a[j]]);
}
add(ans,g[x]),rs[x]=ans;
for(int u=l[x],k;u<r[x];++u) {
k=a[u];
for(int v=l[k],i;v<r[k];++v) {
i=a[v];
if(mu[k/i]==1) add(s[k],g[i]),add(s[k],g[i]);
else if(mu[k/i]==-1) sub(s[k],g[i]),sub(s[k],g[i]);
}
}
for(int i=l[x];i<r[x];++i) sub(f[a[i]],g[a[i]]),sub(f[a[i]],g[a[i]]);
}
int _; scanf("%d",&_);
for(int m;_--;) scanf("%d",&m),printf("%d\n",rs[m]);
return 0;
}
*D. [CF2039G] Shohag Loves Pebae
题目大意
给定一棵
个点的树,给每个点赋 的权值,使得每条路径的长度都不是点权 的因数,且所有点权互质,求方案数。 数据范围:
。
思路分析
观察题目的限制,实际上就是要求
不妨设最长路长度为
但是这样无法满足点权互质的限制,莫比乌斯反演得到方案数为
很显然整除分块,那么我们要算
然后我们要求出所有
注意到根据直径的性质
所以我们只要处理
对于
时间复杂度
代码呈现
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=1e6+5,MOD=998244353;
ll ksm(ll a,ll b) { ll s=1; for(;b;a=a*a%MOD,b>>=1) if(b&1) s=s*a%MOD; return s; }
vector <int> G[MAXN];
int n,lim[MAXN],dep[MAXN],ot[MAXN];
void dfs1(int u,int fz) {
for(int v:G[u]) if(v^fz) dfs1(v,u),dep[u]=max(dep[u],dep[v]+1);
}
void dfs2(int u,int fz) {
int mx=ot[u],smx=0;
for(int v:G[u]) if(v^fz) {
if(mx<dep[v]+1) smx=mx,mx=dep[v]+1;
else smx=max(smx,dep[v]+1);
}
lim[u]=mx+smx+1;
for(int v:G[u]) if(v^fz) ot[v]=(dep[v]+1==mx?smx:mx)+1,dfs2(v,u);
}
int q,up,pr[MAXN],tot,qc[MAXN];
bool isc[MAXN];
ll m,B,vl[MAXN],id1[MAXN],id2[MAXN];
int id(ll x) { return x<=B?id1[x]:id2[m/x]; }
ll g[MAXN],f[MAXN],h[MAXN],dp[MAXN]; //cnt prime, sum mu, minp>?, answer
ll F(ll x) {
if(x<=pr[up]) return x>=1;
return f[id(x)]+up+1;
}
signed main() {
scanf("%d%lld",&n,&m),B=sqrt(m);
for(int i=1,u,v;i<n;++i) scanf("%d%d",&u,&v),G[u].push_back(v),G[v].push_back(u);
dfs1(1,0),dfs2(1,0);
for(int i=2;i<MAXN;++i) {
if(!isc[i]) pr[++tot]=i;
for(int j=1;j<=tot&&i*pr[j]<MAXN;++j) {
isc[i*pr[j]]=true;
if(i%pr[j]==0) break;
}
}
for(int i=1;i<=n;++i) lim[i]=upper_bound(pr+1,pr+tot+1,lim[i])-pr-1;
up=*max_element(lim+1,lim+n+1);
for(ll l=1,r;l<=m;l=r+1) {
r=m/(m/l),vl[++q]=m/l;
if(vl[q]<=B) id1[vl[q]]=q;
else id2[m/vl[q]]=q;
}
for(int i=1;i<=q;++i) g[i]=vl[i]-1;
for(int k=1;k<=tot;++k) {
for(int i=1;i<=q&&1ll*pr[k]*pr[k]<=vl[i];++i) {
g[i]-=g[id(vl[i]/pr[k])]-(k-1);
}
}
for(int i=1;i<=q;++i) f[i]=-g[i];
for(int k=tot;k>up;--k) {
for(int i=1;i<=q&&1ll*pr[k]*pr[k]<=vl[i];++i) {
f[i]-=f[id(vl[i]/pr[k])]+k;
}
}
if(pr[up]>2*B) {
ll ans=1;
for(int i=1;i<=n;++i) ans=ans*(max(0ll,g[1]-lim[i])+1)%MOD;
if(up<g[1]) ans=(ans-(g[1]-up))%MOD;
printf("%lld\n",(ans+MOD)%MOD);
return 0;
}
for(int i=1;i<=n;++i) ++qc[lim[i]];
for(int i=1;i<=q;++i) h[i]=g[i],dp[i]=1;
for(int k=tot;k>=1;--k) {
if(qc[k]) {
for(int i=1;i<=q&&vl[i]>=pr[k];++i) dp[i]=dp[i]*ksm((h[i]-k+1)%MOD,qc[k])%MOD;
}
for(int i=1;i<=q&&1ll*pr[k]*pr[k]<=vl[i];++i) {
for(ll pw=pr[k];pw*pr[k]<=vl[i];pw=pw*pr[k]) {
h[i]+=1+h[id(vl[i]/pw)]-k;
}
}
}
dp[q]=1;
ll ans=0;
for(ll l=1,r;l<=m;l=r+1) {
r=m/(m/l);
ans=(ans+(F(r)-F(l-1))%MOD*dp[id(m/l)])%MOD;
}
printf("%lld\n",(ans+MOD)%MOD);
return 0;
}
*E. [CF2039H2] Cool Swap Walk
题目大意
给定一个
排列 ,定义一次操作为选择一条 的格路,对于经过的每个点 ,依次交换 ,构造一种在 次操作内还原该序列的方法。 数据范围:
。
思路分析
首先这条格路的形态不固定时很难刻画,为了简化操作,我们肯定想让这条格路尽可能在主对角线上移动。
此时从
观察发现这个过程中的
忽略
这就是奇偶排序的过程,因此我们可以在
现在还要考虑上循环移位的问题,不妨钦定操作
暴力模拟奇偶排序的过程。
事实上由于我们的操作不能选择
时间复杂度
代码呈现
#include<bits/stdc++.h>
using namespace std;
const int MAXN=505;
int n,a[MAXN],id[MAXN],b[MAXN];
void solve() {
cin>>n;
for(int i=0;i<n;++i) cin>>a[i],id[i]=i;
if(n==2) return cout<<(a[0]<=a[1]?"0\n":"1\nRD\n"),void();
sort(id,id+n,[&](int x,int y){ return a[x]<a[y]; });
for(int i=0;i<n;++i) a[id[i]]=((i+4)%n+n)%n;
cout<<n+4<<"\n";
for(int i=0;i<n+4;++i) {
for(int j=2;j<n;j+=2) {
int x=(i+j-1)%n,y=(i+j)%n;
if(y&&a[x]>a[y]) swap(a[x],a[y]),cout<<"RRDD";
else cout<<"RDRD";
}
cout<<(n&1?"\n":"RD\n");
}
}
signed main() {
ios::sync_with_stdio(false);
cin.tie(0),cout.tie(0);
int _; cin>>_;
while(_--) solve();
return 0;
}
F. [CF2046D] For the Emperor!
题目大意
给定
个点 条边的有向图,第 个点上有 个机器人,你可以激活若干个点,一个点被激活,上面的机器人就可以沿着图上的边走到其他节点并激活这些点,求激活全部点最少要手动激活多少个点。 数据范围:
。
思路分析
首先强联通分量肯定可以缩点,转成 DAG 问题。
这种图上最优代价的问题肯定考虑网络流,最大流很显然无法描述本题模型,因此需要费用流。
首先我们要把每个点拆成
对于 DAG 上的边,直接连
那么再建虚拟点
钦定
时间复杂度
代码呈现
#include<bits/stdc++.h>
using namespace std;
namespace F {
const int MAXV=605,MAXE=1e4+5,inf=1e9;
struct Edge { int v,e,f,w; } G[MAXE];
int S,T,ec=1,hd[MAXV],dis[MAXV],pre[MAXV];
bool inq[MAXV];
void init() { ec=1,memset(hd,0,sizeof(hd)); }
void adde(int u,int v,int f,int w) { G[++ec]={v,hd[u],f,w},hd[u]=ec; }
void link(int u,int v,int f,int w) { adde(u,v,f,w),adde(v,u,0,-w); }
bool SPFA() {
memset(dis,0x3f,sizeof(dis));
memset(pre,0,sizeof(pre));
memset(inq,false,sizeof(inq));
queue <int> Q; Q.push(S),inq[S]=true,dis[S]=0;
while(Q.size()) {
int u=Q.front(); Q.pop(),inq[u]=false;
for(int i=hd[u],v;i;i=G[i].e) if(G[i].f&&dis[v=G[i].v]>dis[u]+G[i].w) {
dis[v]=dis[u]+G[i].w,pre[v]=i;
if(!inq[v]) Q.push(v),inq[v]=true;
}
}
return pre[T];
}
array<int,2> ssp() {
int f=0,c=0;
while(SPFA()) {
int g=inf;
for(int u=T;u!=S;u=G[pre[u]^1].v) g=min(g,G[pre[u]].f);
f+=g,c+=g*dis[T];
for(int u=T;u!=S;u=G[pre[u]^1].v) G[pre[u]].f-=g,G[pre[u]^1].f+=g;
}
return {f,c};
}
}
const int MAXN=205,inf=1e9;
int n,m,low[MAXN],dfn[MAXN],dcnt,stk[MAXN],tp,bl[MAXN],scnt;
bool ins[MAXN];
int a[MAXN],su[MAXN];
vector <int> G[MAXN];
void tarjan(int u) {
low[u]=dfn[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]) bl[stk[tp]]=scnt,ins[stk[tp--]]=false;
}
}
const int V=1000;
void solve() {
scanf("%d%d",&n,&m),F::init();
for(int i=1;i<=n;++i) cin>>a[i];
for(int i=1,u,v;i<=m;++i) cin>>u>>v,G[u].push_back(v);
for(int i=1;i<=n;++i) if(!dfn[i]) tarjan(i);
for(int i=1;i<=n;++i) {
su[bl[i]]+=a[i];
}
int s=F::S=3*scnt+1,t=F::T=3*scnt+2;
auto L=[&](int x) { return x+scnt; };
auto R=[&](int x) { return x+2*scnt; };
for(int i=1;i<=scnt;++i) {
if(su[i]) {
F::link(s,i,su[i],0);
F::link(i,L(i),1,1);
F::link(i,R(i),inf,0);
}
F::link(L(i),R(i),1,-V);
F::link(L(i),R(i),inf,0);
F::link(R(i),t,inf,0);
}
for(int u=1;u<=n;++u) for(int v:G[u]) if(bl[u]^bl[v]) F::link(R(bl[u]),L(bl[v]),inf,0);
int z=F::ssp()[1]+V*scnt;
printf("%d\n",z<=scnt?z:-1);
for(int i=1;i<=n;++i) a[i]=su[i]=stk[i]=bl[i]=low[i]=dfn[i]=ins[i]=0,G[i].clear();
scnt=dcnt=tp=0;
}
signed main() {
int _; scanf("%d",&_);
while(_--) solve();
return 0;
}
G. [CF2046E2] Cheops and a Contest
题目大意
给定
个人,有属性 ,对于一个题目 ,他能做出来当且仅当 。 已知
个人属于 个队,构造 个 不相等的题目,使得第 队的每个人做出的题目数量严格大于第 队的每个人。 数据范围:
。
思路分析
先从
假设第
如果
此时每个人只能多通过一个题,因此
其次如果有一个
为了尽可能合法,这部分的题目肯定选择最大的合法难度。
然后我们在
这样就完成了
一般的情况完全一样,只不过要求变成所有
依然类似上面的过程,找到所有能放题目的位置贪心地放,最后检验是否合法即可。
时间复杂度
代码呈现
#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;
inline void chkmin(int &x,const int &y) { x=y<x?y:x; }
inline void chkmax(int &x,const int &y) { x=y>x?y:x; }
const int MAXN=3e5+5,inf=2e9;
int n,m,a[MAXN],b[MAXN],ty[MAXN],bl[MAXN];
int L[MAXN],R[MAXN],id[MAXN];
vector <int> g[MAXN];
void shrk(vector<array<int,2>>&vc) {
sort(vc.begin(),vc.end());
vector<array<int,2>> nw;
for(auto i:vc) if(i[0]<=i[1]) {
if(nw.empty()||i[0]>nw.back()[1]+1) nw.push_back(i);
else chkmax(nw.back()[1],i[1]);
}
vc.swap(nw);
}
bool chk(const vector<array<int,2>>&vc,int &x) {
int i=lower_bound(vc.begin(),vc.end(),array<int,2>{x+1,x+1})-vc.begin()-1;
if(i>=0&&x<=vc[i][1]) return x=vc[i][0]-1,true;
return false;
}
int lc[MAXN],rc[MAXN],mn[MAXN],mx[MAXN];
void solve() {
cin>>n>>m;
gp_hash_table <int,bool> uty;
for(int i=1;i<=n;++i) cin>>a[i]>>b[i]>>ty[i],uty[ty[i]]=1,id[i]=i;
vector <array<int,2>> ban,wys;
for(int i=1,x;i<=m;++i) {
cin>>x,g[i].resize(x),L[i]=inf,R[i]=-inf;
for(int &j:g[i]) cin>>j,bl[j]=i,chkmin(L[i],a[j]),chkmax(R[i],a[j]);
if(i>1&&R[i]>L[i-1]) ban.push_back({L[i-1]+1,R[i]});
}
shrk(ban);
sort(id+1,id+n+1,[&](int i,int j){ return a[i]<a[j]; });
lc[0]=m+1,rc[n+1]=0;
for(int i=1;i<=n;++i) lc[i]=min(lc[i-1],bl[id[i]]);
for(int i=n;i>=1;--i) rc[i]=max(rc[i+1],bl[id[i]]);
for(int i=2,x=0;i<=n;++i) if(a[id[i-1]]<a[id[i]]&&rc[i]<=lc[i-1]) {
for(int o=0;o<2;++o) {
while(uty.find(++x)!=uty.end());
wys.push_back({a[id[i]],x});
}
}
gp_hash_table <int,int> d;
gp_hash_table <int,vector<array<int,2>>> banc;
for(int i=1;i<m;++i) for(int j:g[i]) {
if(a[j]<=R[i+1]) {
if(d.find(ty[j])==d.end()) d[ty[j]]=b[j];
else chkmin(d[ty[j]],b[j]);
}
}
for(int i=2;i<=m;++i) for(int j:g[i]) {
if(a[j]>=L[i-1]) banc[ty[j]].push_back({L[i-1]+1,b[j]});
}
for(auto &it:d) {
auto &cb=banc[it.first];
int &x=it.second; shrk(cb);
while(chk(ban,x)||chk(cb,x));
wys.push_back({x,it.first});
}
sort(wys.begin(),wys.end());
for(int i=1;i<=m;++i) mn[i]=inf,mx[i]=-inf;
for(int i=1;i<=n;++i) {
int vl=lower_bound(wys.begin(),wys.end(),array<int,2>{a[i]+1,-1})-wys.begin();
if(d.find(ty[i])!=d.end()&&a[i]<d[ty[i]]&&d[ty[i]]<=b[i]) ++vl;
chkmin(mn[bl[i]],vl),chkmax(mx[bl[i]],vl);
}
bool ok=true;
for(int i=1;i<m;++i) ok&=mn[i]>mx[i+1];
if(!ok) cout<<"-1\n";
else {
cout<<wys.size()<<"\n";
for(auto z:wys) cout<<z[0]<<" "<<z[1]<<"\n";
}
for(int i=1;i<=m;++i) g[i].clear();
}
signed main() {
ios::sync_with_stdio(false);
cin.tie(0),cout.tie(0);
int _; cin>>_;
while(_--) solve();
return 0;
}
*H. [CF2046F2] Yandex Cuneiform
题目大意
给定
Y,D,X,?
构成的长度为的字符串 ,把 ?
填成Y,D,X
的某一种,使得该可以由如下方式构造:从空串开始,每次向串中插入一个 Y,D,X
,并且每次插入之后中不存在相邻相同的字符,给出方案。 数据范围:
。
思路分析
先解决不存在 ?
的情况,
假设 Y,D,X
,并且使得得到的新
不妨假设 Y,D,X
个数相等,因此 DX
或 XD
子串
如果
否则一定有
因此用链表动态维护 set
维护每种长度为
接下来回到原问题,我们要找到一个
此时提取出所有极长 ?
连续段,可以发现可能的 Y,D
个数构成一个凸包,把每个连续段的凸包求闵可夫斯基和,并判断
但这种方法太难实现,我们通过打表发现
那么直接 dp
时间复杂度
代码呈现
#include<bits/stdc++.h>
using namespace std;
inline void chkmin(int &x,const int &y) { x=y<x?y:x; }
inline void chkmax(int &x,const int &y) { x=y>x?y:x; }
const int MAXN=2e5+5;
int n,a[MAXN],pr[MAXN],sf[MAXN],wys[MAXN];
char s[MAXN];
set <int> ps[4];
struct FenwickTree {
int tr[MAXN],s;
void init() { fill(tr+1,tr+n+1,0); }
void add(int x,int v) { for(;x<=n;x+=x&-x) tr[x]+=v; }
int qry(int x) { for(s=0;x;x&=x-1) s+=tr[x]; return s; }
} TR;
int L[MAXN][4][4],R[MAXN][4][4];
void solve() {
scanf("%s",s+1),n=strlen(s+1);
for(int i=1;i<=n;++i) a[i]=(s[i]=='?'?0:(s[i]=='D'?1:(s[i]=='X'?2:3)));
for(int i=1;i<=n;++i) {
memset(L[i],0x3f,sizeof(L[i]));
memset(R[i],-0x3f,sizeof(R[i]));
for(int j:{1,2,3}) if(!a[i]||a[i]==j) {
for(int k:{1,2,3}) for(int c:{1,2,3}) if(c^j) {
chkmin(L[i][j][k],L[i-1][c][k]+(j==k));
chkmax(R[i][j][k],R[i-1][c][k]+(j==k));
}
}
}
int c[4]={0,n/3,n/3,n/3};
for(int i=n;i>=1;--i) {
a[i]=0;
for(int j:{1,2,3}) if(i==n||j!=a[i+1]) {
bool ok=true;
for(int k:{1,2,3}) ok&=(L[i][j][k]<=c[k]&&c[k]<=R[i][j][k]);
if(ok) { a[i]=j; break; }
}
if(!a[i]) return puts("NO"),void();
s[i]=" DXY"[a[i]],--c[a[i]];
}
for(int i=1;i<=n;++i) pr[i]=i-1,sf[i]=i+1;
pr[n+1]=n,sf[0]=1;
for(int i=1;i<n;++i) ps[a[i]^a[i+1]].insert(i);
int tp=n;
auto del=[&](int x) {
wys[tp--]=x;
int l=pr[x],r=sf[x];
if(l>=1) ps[a[l]^a[x]].erase(l);
if(r<=n) ps[a[x]^a[r]].erase(x);
if(l>=1&&r<=n) ps[a[l]^a[r]].insert(l);
sf[l]=r,pr[r]=l;
};
for(int _=0;_<n/3;++_) {
int y=*ps[a[sf[0]]].begin(),z=sf[y],x=(sf[z]<=n&&a[pr[y]]==a[sf[z]]?pr[y]:sf[0]);
del(z),del(y),del(x);
}
printf("YES\n%s\n",s+1);
for(int i=1;i<=n;++i) {
printf("%c %d%c"," DXY"[a[wys[i]]],TR.qry(wys[i])," \n"[i%3==0]);
TR.add(wys[i],1);
}
TR.init();
for(int i:{1,2,3}) ps[i].clear();
}
signed main() {
int _; scanf("%d",&_);
while(_--) solve();
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通