2024 Noip 做题记录(八)
Round #29 - 2024.11.6
A. Add One 2
题目大意
给定
,初始 全为 ,每次可以花 的代价给一个长度为 的前缀或后缀 ,求让所有 的最小代价。 数据范围:
。
思路分析
考虑怎样的
观察差分,我们发现如果
将这些操作撤销能够得到
为了方便,在
考虑把
即
可以发现每次找到一段连续的局部最小值(
此时每次操作的区间都是笛卡尔树上的一个子树,按子树大小从小到大贪心即可。
时间复杂度
代码呈现
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=1e6+5,inf=1e9+7;
int n,l[MAXN],r[MAXN],a[MAXN],st[MAXN],tp;
void solve() {
cin>>n,a[0]=a[n+1]=inf;
for(int i=1;i<=n;++i) cin>>a[i];
st[tp=0]=0;
for(int i=1;i<=n;++i) {
while(a[st[tp]]<a[i]) --tp;
l[i]=st[tp],st[++tp]=i;
}
st[tp=0]=n+1;
for(int i=n;i>=1;--i) {
while(a[st[tp]]<=a[i]) --tp;
r[i]=st[tp],st[++tp]=i;
}
vector <array<int,2>> q;
for(int i=1;i<=n;++i) if(a[i]<a[l[i]]&&a[i]<a[r[i]]) {
q.push_back({r[i]-l[i]-1,min(a[l[i]],a[r[i]])-a[i]});
}
ll w=-2*inf,s=accumulate(a+1,a+n+1,0ll);
for(int i=0;i<=n;++i) w+=abs(a[i+1]-a[i]);
sort(q.begin(),q.end()),w>>=1;
for(auto i:q) {
if(w<=0) break;
s+=min(w,(ll)i[1])*i[0],w=max(0ll,w-i[1]);
}
cout<<s<<"\n";
}
signed main() {
ios::sync_with_stdio(false);
int T; cin>>T;
while(T--) solve();
return 0;
}
K. Sticks
题目大意
定义一个
的 01 网格,一次操作可以把某一行或某一列的全 前缀变成 。 如果一个网格是好的,当且仅当其可以从全
网格进行若干次操作得到。 给定一个带
?
的 01 网格,求有多少种给?
填 01 的方法使得其是好的。数据范围:
。
思路分析
题目限制相当于每行每列覆盖一个前缀
对于每种覆盖方式计数更为简单,考虑把每个好的网格唯一对应到一种覆盖方式上。
这就是 AGC035F,因此唯一的要求是不存在
那么可以 dp,
时间复杂度
代码呈现
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=3005,MOD=998244353;
char s[MAXN][MAXN];
ll f[MAXN],vc[MAXN];
bool okc[MAXN];
signed main() {
int n;
scanf("%d",&n);
for(int i=1;i<=n;++i) scanf("%s",s[i]+1),s[i][0]=s[0][i]='1',vc[i]=okc[i]=1;
f[0]=vc[0]=okc[0]=1;
for(int i=1;i<=n;++i){
ll sf=0,vr=0;
bool okr=1;
for(int j=0;j<=n;++j) {
if(s[i][j]=='0') okr=0;
else if(s[i][j]=='1') vr=okr;
else vr+=okr;
ll w=f[j]*vr%MOD;
if(okr) w=(w+sf*(vc[j]-okc[j]))%MOD;
sf=(sf*vc[j]+f[j])%MOD,f[j]=w;
}
for(int j=0;j<=n;++j) {
if(s[i][j]=='0') okc[j]=0;
else if(s[i][j]=='1') vc[j]=okc[j];
else vc[j]+=okc[j];
}
}
ll ans=0;
for(int j=0;j<=n;++j) ans=(ans*vc[j]+f[j])%MOD;
printf("%lld\n",ans);
return 0;
}
M. Bot Friends
题目大意
给定
数轴,第 个球在 上, 上有洞,你需要以某种顺序依次推每个球。 每个球会沿其指定方向运动(有些球指定方向可以自由确定),进入第一个没有球的洞中。
一个球是好的当且仅当其进洞且不在左右相邻的洞中。
数据范围:
。
思路分析
观察每个球和洞的匹配,其合法当且仅当这些匹配对应的线段在数轴上两两包含或相交。
那么可以做一个形似括号序列的 dp,
转移的时候考虑这个点的匹配形式,需要考虑把这个点前面的洞预留出来给后面的球的转移。
时间复杂度
代码呈现
#include<bits/stdc++.h>
using namespace std;
const int MAXN=5005,inf=1e9;
char s[MAXN];
int n,f[MAXN][MAXN],g[MAXN][MAXN];
inline void chkmax(int &x,int y) { x=x>y?x:y; }
void solve() {
scanf("%s",s+1),n=strlen(s+1);
for(int i=0;i<=n;++i) for(int j=0;j<=n;++j) f[i][j]=g[i][j]=-inf;
f[0][0]=0;
for(int i=1;i<=n;++i) {
if(s[i]!='>') chkmax(f[i][0],f[i-1][0]);
if(s[i]!='<') chkmax(g[i][1],f[i-1][0]);
for(int j=1;j<i;++j) {
if(s[i]!='>') {
chkmax(f[i][j-1],max(f[i-1][j]+2,g[i-1][j]+1));
chkmax(f[i][j],g[i-1][j]);
}
if(s[i]!='<') {
chkmax(g[i][j],f[i-1][j]+1);
chkmax(g[i][j+1],max(f[i-1][j],g[i-1][j]));
}
}
}
int ans=0;
for(int j=0;j<=n;++j) chkmax(ans,max(f[n][j]+(j>0),g[n][j]));
printf("%d\n",ans);
}
signed main() {
int T; scanf("%d",&T);
while(T--) solve();
return 0;
}
*L. Exchanging Kubic
题目大意
交互器有序列
,你可以询问 次某个区间的最大子段和,构造出一个 使得 与 每个区间的最大子段和都相等。 数据范围:
。
思路分析
询问所有
那么这个序列会变成正负交错的一个序列,选取其中的一个
如果得到的值不是
否则我们知道
为了把
如果这两个询问中有一个能确定
否则我们知道
因此我们可以把这三个元素也缩起来,并且把
容易发现
时间复杂度
代码呈现
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=2005;
struct seg { int l,r; ll s; };
int n;
ll a[MAXN];
ll qry(int l,int r) {
cout<<"? "<<l<<" "<<r<<endl;
ll z; cin>>z; return z;
}
void solve() {
cin>>n;
vector <seg> p;
for(int i=1;i<=n;++i) {
a[i]=qry(i,i);
if(p.size()&&(p.back().s>0)==(a[i]>0)) p.back().s+=a[i],p.back().r=i;
else p.push_back({i,i,a[i]});
}
if(!p.front().s) p.erase(p.begin());
if(!p.back().s) p.erase(--p.end());
auto pop=[&](int l,int r) { p.erase(p.begin()+l,p.begin()+r+1); };
while(p.size()>1) {
int i=0;
for(int j=0;j<(int)p.size();j+=2) if(p[j].s<p[i].s) i=j;
if(i-2>=0) {
ll x=qry(p[i-2].l,p[i].r);
if(x>p[i-2].s) {
a[p[i-1].l]=x-p[i-2].s-p[i].s;
p[i-2].r=p[i].r,p[i-2].s=x,pop(i-1,i);
continue;
}
}
if(i+2<(int)p.size()) {
ll x=qry(p[i].l,p[i+2].r);
if(x>p[i+2].s) {
a[p[i+1].l]=x-p[i+2].s-p[i].s;
p[i+2].l=p[i].l,p[i+2].s=x,pop(i,i+1);
continue;
}
}
if(!i) a[p[i+1].l]=-p[i].s,pop(i,i+1);
else if(i+1==(int)p.size()) a[p[i-1].l]=-p[i].s,pop(i-1,i);
else a[p[i+1].l]=-p[i].s,p[i-1].r=p[i+1].r,pop(i,i+1);
}
cout<<"! "; for(int i=1;i<=n;++i) cout<<a[i]<<" "; cout<<endl;
}
signed main() {
int T; cin>>T;
while(T--) solve();
return 0;
}
*B. Periodic Sequence
题目大意
给定
,对于 ,求出最长的周期字符串序列,满足其中的元素两两不同,且长度 。 一个字符串序列
是周期字符串序列,当且仅当所有 都是 的周期。 特别地:如果
是 的前缀,那么 也是 的一个周期。 数据范围:
。
思路分析
先刻画
首先
因此最优解一定有
通过归纳法可以发现这个序列中的字符串一定是由
对于每个这样方法构造出的字符串,我们可以把他们都安排到一个序列中,而这就是答案。
具体构造时先把所有字符串插入 Trie,每个节点只有两个儿子,一个是
优先遍历
具体可以取
那么我们就要对每个
枚举
其中求和号外面的
化简得到:
我们要求
对于每个
对于
很显然后面的和式在
从大到小枚举每一项
这些过程都可以
对
时间复杂度
代码呈现
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=2e5+5;
int n,MOD;
ll dp[MAXN],f[MAXN],g[MAXN];
void dp1(int k) {
memset(f,0,sizeof(f));
f[k]=1;
for(int i=k+1;i<=n;++i) f[i]=(2*f[i-1]+MOD-f[i-k-1])%MOD;
for(int i=1;i<=n;++i) dp[i]=(dp[i]+f[i])%MOD;
}
signed main() {
scanf("%d%d",&n,&MOD);
int B=sqrt(n);
for(int i=1;i<=B;++i) dp1(i);
for(int x=B;x>=0;--x) {
for(int k=B+1;k<=n;++k) {
int i=(k+1)*x+k;
if(i<=n) g[i]=(g[i]+(x&1?MOD-1:1))%MOD;
}
for(int i=1;i<=n;++i) g[i]=(g[i]+2*g[i-1])%MOD;
}
for(int i=1;i<=n;++i) dp[i]=(dp[i]+g[i])%MOD;
for(int i=1;i<=n;++i) printf("%lld ",dp[i]); puts("");
return 0;
}
*E. Min or Max 2
题目大意
给定两个
排列 ,初始 ,对于 ,依次选择把 变成 或 。 数据范围:
。
思路分析
可以证明对于每个所有最终合法的
因此每个
考虑求最大可能的
分析发现我们只关心每对
那么每个位置对应一个大小为
我们发现增加
时间复杂度
代码呈现
#include<bits/stdc++.h>
using namespace std;
typedef array<int,3> info;
const int MAXN=5e5+5;
const info I[3]={{0,0,1},{0,1,1},{1,1,2}};
int n,X,Y,a[MAXN],b[MAXN];
inline info operator +(const info &u,const info &v) { return {v[u[0]],v[u[1]],v[u[2]]}; }
struct SegmentTree {
static const int N=1<<19;
info tr[N<<1];
void init() { fill(tr,tr+(N<<1),info{0,1,2}); }
void upd(int x,info o) {
for(tr[x+=N]=o,x>>=1;x;x>>=1) tr[x]=tr[x<<1]+tr[x<<1|1];
}
} T;
int pa[MAXN],pb[MAXN],mx[MAXN];
void sol() {
X=0,Y=1;
for(int i=1;i<=n;++i) pa[a[i]]=pb[b[i]]=i;
auto ins=[&](int i) { if(i>1) T.upd(i,I[(a[i]>X)+(b[i]<Y)]); };
for(int i=2;i<=n;++i) ins(i);
for(X=1;X<=n;++X) {
ins(pa[X]);
while(Y<=n&&!T.tr[1][(a[1]>X)+(b[1]<Y)]) ++Y,ins(pb[Y-1]);
mx[X]=Y-1;
}
for(int i=2;i<=n;++i) T.upd(i,{0,1,2});
}
int l[MAXN],r[MAXN],f[MAXN*2];
void solve() {
cin>>n,fill(f,f+2*n+1,0);
for(int i=1;i<=n;++i) cin>>a[i];
for(int i=1;i<=n;++i) cin>>b[i];
sol();
for(int i=1;i<=n;++i) r[i]=mx[i],a[i]=n-a[i]+1,b[i]=n-b[i]+1;
sol();
for(int i=1;i<=n;++i) l[n-i+1]=n-mx[i]+1;
for(int i=1;i<=n;++i) {
++f[l[i]+n-i],--f[r[i]+n-i+1];
}
for(int i=1;i<=2*n;++i) f[i]+=f[i-1];
cout<<f[n]<<" ";
for(int i=1;i<n;++i) cout<<f[n+i]+f[n-i]<<" "; cout<<"\n";
}
signed main() {
ios::sync_with_stdio(false);
int _; cin>>_,T.init();
while(_--) solve();
return 0;
}
Round #30 - 2024.11.12
Contest Link: 2024 ICPC Kunming Invitational Contest
E. Relearn through Review
题目大意
给定
,选定其中一个区间 ,给 加上 ,最大化 。 数据范围:
。
思路分析
设
因此我们从小到大枚举
因此直接枚举满足条件的
时间复杂度
代码呈现
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=3e5+5;
ll gcd(ll x,ll y) {
if(!x||!y) return x|y;
return gcd(y,x%y);
}
ll a[MAXN],k,pre[MAXN],suf[MAXN],val[MAXN];
void solve() {
int n;
cin>>n>>k;
for(int i=1;i<=n;++i) cin>>a[i];
pre[0]=suf[n+1]=val[0]=0;
for(int i=1;i<=n;++i) pre[i]=gcd(pre[i-1],a[i]),val[i]=gcd(val[i-1],a[i]+k);
for(int i=n;i>=1;--i) suf[i]=gcd(suf[i+1],a[i]);
ll ans=pre[n];
for(int i=1;i<=n;++i) ans=max(ans,gcd(val[i],suf[i+1]));
for(int l=2;l<=n;++l) {
val[l-1]=0;
for(int r=l;r<=n;++r) {
ll z=gcd(val[r-1],a[r]+k);
if(z==val[r]) break;
val[r]=z,ans=max(ans,gcd(z,gcd(pre[l-1],suf[r+1])));
}
}
cout<<ans<<"\n";
}
signed main() {
ios::sync_with_stdio(false);
int T; cin>>T;
while(T--) solve();
return 0;
}
J. The Quest for El Dorado
题目大意
给定
个点 条带权边的无向图,每条边有颜色,从 出发顺次进行 步操作,第 次沿 颜色的边走 的距离,求最终能到达哪些点。 数据范围:
。
思路分析
考虑对每种颜色的图维护一个 Dijkstra,取出到当前点集距离
时间复杂度
代码呈现
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=5e5+5;
const ll inf=1e18;
int n,E,m;
priority_queue <array<ll,2>,vector<array<ll,2>>,greater<array<ll,2>>> Q[MAXN];
ll dis[MAXN<<1];
struct Edge { int v,w; };
vector <Edge> G[MAXN<<1];
int tot=0,ver[MAXN<<1];
map <int,int> id[MAXN];
int ID(int u,int c) {
if(!id[u].count(c)) {
id[u][c]=++tot;
ver[tot]=u;
}
return id[u][c];
}
void ins(int u) {
for(auto it:id[u]) {
int c=it.first,z=it.second;
dis[z]=0;
for(auto e:G[z]) {
if(dis[e.v]>e.w) {
Q[c].push({dis[e.v]=e.w,e.v});
}
}
}
}
bool ans[MAXN];
void solve() {
cin>>n>>E>>m;
for(int i=1,u,v,c,w;i<=E;++i) {
cin>>u>>v>>c>>w;
int x=ID(u,c),y=ID(v,c);
G[x].push_back({y,w});
G[y].push_back({x,w});
}
ins(1),ans[1]=true;
for(int c,lim;m--;) {
cin>>c>>lim;
vector <int> cur;
while(Q[c].size()&&Q[c].top()[0]<=lim) {
int i=Q[c].top()[1]; Q[c].pop();
if(ans[ver[i]]) continue;
ans[ver[i]]=true,cur.push_back(ver[i]);
for(auto e:G[i]) if(dis[e.v]>dis[i]+e.w) {
Q[c].push({dis[e.v]=dis[i]+e.w,e.v});
}
}
for(int u:cur) ins(u);
}
for(int i=1;i<=n;++i) cout<<ans[i];
cout<<"\n";
for(int i=1;i<=n;++i) id[i].clear(),ans[i]=0;
for(int i=1;i<=tot;++i) G[i].clear(),dis[i]=inf;
for(int i=1;i<=E;++i) while(Q[i].size()) Q[i].pop();
tot=0;
return ;
}
signed main() {
ios::sync_with_stdio(false);
fill(dis,dis+(MAXN<<1),inf);
int T; cin>>T;
while(T--) solve();
return 0;
}
L. Trails
题目大意
给定
网格图, 条额外边连接 ,求 到每个点的距离之和。 数据范围:
。
思路分析
首先一个点
即找到最多的
那么先加上所有点的
先求出到每个
显然
时间复杂度
代码呈现
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=1e6+5;
struct FenwickTree {
int tr[MAXN]; vector <int> e;
void upd(int x,int v) { for(e.push_back(x);x<MAXN;x+=x&-x) tr[x]=max(tr[x],v); }
int qry(int x) { int s=0; for(;x;x&=x-1) s=max(s,tr[x]); return s; }
void init() { for(int x:e) for(;x<MAXN;x+=x&-x) tr[x]=0; e.clear(); }
} T;
int n,p,q,dp[MAXN];
struct poi { int x,y; } a[MAXN];
vector <poi> f[MAXN];
void solve() {
cin>>n>>p>>q,T.init();
for(int i=1;i<=n;++i) cin>>a[i].x>>a[i].y;
sort(a+1,a+n+1,[&](auto u,auto v){ return u.x^v.x?u.x<v.x:u.y>v.y; });
int len=0;
for(int i=1;i<=n;++i) {
dp[i]=T.qry(a[i].y)+1,len=max(len,dp[i]);
if(a[i].x<p&&a[i].y<q) f[dp[i]].push_back({a[i].x+1,a[i].y+1});
T.upd(a[i].y+1,dp[i]);
}
ll ans=1ll*p*(p+1)/2*(q+1)+1ll*q*(q+1)/2*(p+1);
for(int x=1;x<=len;++x) {
f[x].push_back({p+1,0});
for(int i=0;i+1<(int)f[x].size();++i) {
ans-=1ll*(f[x][i+1].x-f[x][i].x)*(q-f[x][i].y+1);
}
f[x].clear();
}
cout<<ans<<"\n";
}
signed main() {
ios::sync_with_stdio(false);
int _; cin>>_;
while(_--) solve();
return 0;
}
F. Collect the Coins
题目大意
枚硬币,第 枚恰好在 时刻出现在 ,有两个机器人要接住所有硬币,求最大速度的最小值。 数据范围:
。
思路分析
先二分速度
用最小链覆盖刻画,如果
用 Dilworth 定理转成求最长反链,那么
-
如果
,那么 且 ,化简得到 。 -
如果
,那么条件变成 。
只要上述条件满足其一,容易发现这相当于
那么我们只需要检验这些点的 LIS 长度是否
按横坐标排序从小到大扫描,维护 LIS 长度为
时间复杂度
代码呈现
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=1e6+5,inf=1e9;
const ll INF=1e18;
int n,t[MAXN],x[MAXN];
struct poi { ll x,y; } a[MAXN];
bool f[MAXN];
bool chk(ll v) {
for(int i=1;i<=n;++i) a[i]={x[i]-1ll*v*t[i],x[i]+1ll*v*t[i]},f[i]=false;
sort(a+1,a+n+1,[&](auto i,auto j){ return i.x^j.x?i.x<j.x:i.y<j.y; });
ll Y0=INF,Y1=INF;
for(int i=1,j;i<=n;i=j) {
for(j=i;j<=n&&a[j].x==a[i].x;++j);
for(int k=i;k<j;++k) {
if(Y1<a[k].y) return false;
if(Y0<a[k].y) f[k]=true;
}
for(int k=i;k<j;++k) {
Y0=min(Y0,a[k].y);
if(f[k]) Y1=min(Y1,a[k].y);
}
}
return true;
}
void solve() {
cin>>n;
for(int i=1;i<=n;++i) cin>>t[i]>>x[i];
int l=0,r=inf,v=-1;
while(l<=r) {
int mid=(l+r)>>1;
if(chk(mid)) v=mid,r=mid-1;
else l=mid+1;
}
cout<<v<<"\n";
}
signed main() {
ios::sync_with_stdio(false);
int T; cin>>T;
while(T--) solve();
return 0;
}
H. Subarray
题目大意
给定
,对每个 求 中有多少子区间的最大值恰好出现了 次。 数据范围:
。
思路分析
考虑枚举最大值
假设这个 01 序列是
即
时间复杂度
代码呈现
#include<bits/stdc++.h>
using namespace std;
namespace P {
const int MOD=998244353,N=1<<20,G=3;
int rev[N],inv[N],fac[N],ifac[N],w[N<<1];
int ksm(int a,int b=MOD-2) {
int ret=1;
for(;b;a=1ll*a*a%MOD,b=b>>1) if(b&1) ret=1ll*ret*a%MOD;
return ret;
}
void poly_init() {
inv[1]=1;
for(int i=2;i<N;++i) inv[i]=1ll*(MOD-MOD/i)*inv[MOD%i]%MOD;
fac[0]=ifac[0]=1;
for(int i=1;i<N;++i) fac[i]=1ll*fac[i-1]*i%MOD,ifac[i]=1ll*ifac[i-1]*inv[i]%MOD;
for(int k=1;k<=N;k<<=1) {
int x=ksm(G,(MOD-1)/k); w[k]=1;
for(int i=1;i<k;++i) w[i+k]=1ll*x*w[i+k-1]%MOD;
}
}
int plen(int x) { int y=1; for(;y<x;y<<=1); return y; }
void ntt(int *f,bool idft,int n) {
for(int i=0;i<n;++i) {
rev[i]=(rev[i>>1]>>1);
if(i&1) rev[i]|=n>>1;
}
for(int i=0;i<n;++i) if(rev[i]<i) swap(f[i],f[rev[i]]);
for(int k=2,x,y;k<=n;k<<=1) {
for(int i=0;i<n;i+=k) {
for(int j=i;j<i+k/2;++j) {
x=f[j],y=1ll*f[j+k/2]*w[k+j-i]%MOD;
f[j]=(x+y>=MOD)?x+y-MOD:x+y,f[j+k/2]=(x>=y)?x-y:x+MOD-y;
}
}
}
if(idft) {
reverse(f+1,f+n);
for(int i=0,x=ksm(n);i<n;++i) f[i]=1ll*f[i]*x%MOD;
}
}
void poly_mul(const int *f,const int *g,int *h,int n,int m) {
static int a[N],b[N];
for(int i=0;i<n;++i) a[i]=f[i];
for(int i=0;i<m;++i) b[i]=g[i];
int len=plen(n+m-1);
ntt(a,0,len),ntt(b,0,len);
for(int i=0;i<len;++i) h[i]=1ll*a[i]*b[i]%MOD;
ntt(h,1,len);
memset(a,0,sizeof(int)*len);
memset(b,0,sizeof(int)*len);
}
}
const int MAXN=4e5+5,N=1<<20,MOD=998244353;
int n,a[MAXN],st[MAXN],tp,L[MAXN],R[MAXN],nxt[MAXN];
int m,b[MAXN],X[N],Y[N],Z[N],f[MAXN];
void calc() {
for(int i=0;i<=m;++i) {
X[i]=b[i+1]-b[i]-(i==m);
Y[i]=b[m+1-i]-b[m-i]-(i==m);
}
P::poly_mul(X,Y,Z,m+1,m+1);
for(int i=0;i<m;++i) f[m-i]=(f[m-i]+Z[i])%MOD;
}
void solve() {
cin>>n;
map <int,int> pos;
for(int i=1;i<=n;++i) cin>>a[i],f[i]=0;
for(int i=n;i>=1;--i) {
if(!pos.count(a[i])) nxt[i]=n+2;
else nxt[i]=pos[a[i]];
pos[a[i]]=i;
}
for(int i=1;i<=n;++i) {
while(tp&&a[st[tp]]<a[i]) R[st[tp--]]=i;
st[++tp]=i;
}
while(tp) R[st[tp--]]=n+1;
for(int i=n;i>=1;--i) {
while(tp&&a[st[tp]]<a[i]) L[st[tp--]]=i;
st[++tp]=i;
}
while(tp) L[st[tp--]]=0;
for(auto it:pos) {
int u=it.second;
while(u<=n) {
int v=u; b[0]=L[u],m=0;
while(v<R[u]) b[++m]=v,v=nxt[v];
b[m+1]=R[u],calc(),u=v;
}
}
int ans=0;
for(int i=1;i<=n;++i) ans=(ans+1ll*f[i]*f[i]%MOD*i)%MOD;
cout<<ans<<"\n";
}
signed main() {
P::poly_init();
ios::sync_with_stdio(false);
int T; cin>>T;
while(T--) solve();
return 0;
}
K. Permutation
题目大意
交互器有一个
阶排列 ,每次可以询问一个序列 与 的海明距离,在 次询问内还原 。 数据范围:
。
思路分析
考虑分治,每次只要解决每个元素是在序列的前一半还是后一半出现的问题即可。
对于每个元素,依次将其填满在前一半,可以用
需要一定的常数优化。
容易发现此时序列的后一半被浪费了,如果我们同时填两个元素,即
-
那么当询问的答案为
时,可以直接确定 属于哪一半。 -
如果询问答案为
,说明 属于序列的同一半,那么把 和 看成同一个元素,相当于只确定 。
每次询问前随机一个交互顺序,那么每次询问相邻两个元素,如果属于同一半,那么一次询问只能确定一个元素,否则能确定两个元素。
那么每次询问期望能确定
时间复杂度
代码呈现
#include<bits/stdc++.h>
using namespace std;
const int MAXN=1005;
int n,a[MAXN],p[MAXN];
int qry() {
cout<<"0 "; for(int i=1;i<=n;++i) cout<<a[i]<<" "; cout<<endl;
int x; cin>>x; return x;
}
mt19937 rnd(time(0));
void solve(int l,int r,vector<int>&v) {
if(l==r) return p[l]=v[0],void();
int mid=(l+r)>>1;
shuffle(v.begin(),v.end(),rnd);
vector <int> lv,rv,st;
for(int x:v) {
if((int)lv.size()==mid-l+1) { rv.push_back(x); continue; }
if((int)rv.size()==r-mid) { lv.push_back(x); continue; }
if(st.empty()) { st.push_back(x); continue; }
fill(a+1,a+n+1,st[0]);
fill(a+l,a+mid+1,x);
int z=qry();
if(z==0) {
rv.push_back(x);
lv.insert(lv.end(),st.begin(),st.end());
st.clear();
} else if(z==2) {
lv.push_back(x);
rv.insert(rv.end(),st.begin(),st.end());
st.clear();
} else st.push_back(x);
}
if((int)lv.size()<mid-l+1) lv.insert(lv.end(),st.begin(),st.end());
else rv.insert(rv.end(),st.begin(),st.end());
solve(l,mid,lv),solve(mid+1,r,rv);
}
signed main() {
cin>>n;
vector <int> v;
for(int i=1;i<=n;++i) v.push_back(i);
solve(1,n,v);
cout<<"1 "; for(int i=1;i<=n;++i) cout<<p[i]<<" "; cout<<endl;
return 0;
}
C. Stop the Castle 2
题目大意
网格上有
个棋子和 个障碍,每对同行同列的棋子如果中间没有障碍就会互相攻击,现在删除 个障碍,构造方案最小化互相攻击的棋子对数。 数据范围:
。
思路分析
转成放置
那么我们只要最大化能阻挡
求出最大匹配后,如果还有剩余的障碍,且还能阻挡棋子,那么直接放置即可。
时间复杂度
代码呈现
#include<bits/stdc++.h>
using namespace std;
namespace F {
const int MAXV=2e5+5,MAXE=3e6+5,inf=1e9;
struct Edge {
int v,f,lst;
} G[MAXE];
int S,T,tot=1,siz,hd[MAXV],cur[MAXV],dep[MAXV];
void init() { tot=1,memset(hd,0,sizeof(int)*(siz+1)); }
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-1; }
bool BFS() {
memcpy(cur,hd,sizeof(int)*(siz+1));
memset(dep,-1,sizeof(int)*(siz+1));
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=2e5+5;
int n,m,K,L[MAXN],R[MAXN],e[MAXN];
bool vis[MAXN],vl[MAXN],vr[MAXN];
struct poi { int x,y,i; } a[MAXN];
void solve() {
cin>>n>>m>>K,F::init(),K=m-K;
for(int i=1;i<=n;++i) cin>>a[i].x>>a[i].y,a[i].i=-1;
for(int i=1;i<=m;++i) cin>>a[i+n].x>>a[i+n].y,a[i+n].i=i;
for(int i=1;i<=m;++i) L[i]=R[i]=vis[i]=0;
int cl=0,cr=0,ot=0;
sort(a+1,a+n+m+1,[&](auto u,auto v){ return u.x^v.x?u.x<v.x:u.y<v.y; });
for(int i=1,j;i<=n+m;i=j) {
for(j=i;j<=n+m&&a[j].x==a[i].x;++j);
for(int k=i,p=0;k<j;++k) if(a[k].i==-1) {
if(p&&p!=k-1) {
++cl;
for(int o=p+1;o<=k-1;++o) L[a[o].i]=cl;
} else if(p) ++ot;
p=k;
}
}
sort(a+1,a+n+m+1,[&](auto u,auto v){ return u.y^v.y?u.y<v.y:u.x<v.x; });
for(int i=1,j;i<=n+m;i=j) {
for(j=i;j<=n+m&&a[j].y==a[i].y;++j);
for(int k=i,p=0;k<j;++k) if(a[k].i==-1) {
if(p&&p!=k-1) {
++cr;
for(int o=p+1;o<=k-1;++o) R[a[o].i]=cr;
} else if(p) ++ot;
p=k;
}
}
int s=F::S=cl+cr+1,t=F::T=F::siz=s+1;
for(int i=1;i<=m;++i) if(L[i]&&R[i]) e[i]=F::link(L[i],cl+R[i],1);
for(int i=1;i<=cl;++i) F::link(s,i,1),vl[i]=0;
for(int i=1;i<=cr;++i) F::link(cl+i,t,1),vr[i]=0;
int w=F::Dinic();
if(K<=w) {
cout<<cl+cr-2*K+ot<<"\n";
for(int i=1;i<=m&&K;++i) if(L[i]&&R[i]&&!F::G[e[i]].f) vis[i]=true,--K;
} else {
cout<<max(cl+cr-2*w-(K-w),0)+ot<<"\n";
for(int i=1;i<=m&&K;++i) if(L[i]&&R[i]&&!F::G[e[i]].f) vis[i]=vl[L[i]]=vr[R[i]]=true,--K;
for(int i=1;i<=m&&K;++i) if(!vis[i]) {
if(L[i]&&!vl[L[i]]) vis[i]=vl[L[i]]=true,--K;
if(R[i]&&!vr[R[i]]) vis[i]=vr[R[i]]=true,--K;
}
for(int i=1;i<=m&&K;++i) if(!vis[i]) vis[i]=true,--K;
}
for(int i=1;i<=m;++i) if(!vis[i]) cout<<i<<" "; cout<<"\n";
}
signed main() {
ios::sync_with_stdio(false);
int T; cin>>T;
while(T--) solve();
return 0;
}
*D. Generated String
题目大意
动态维护字符串集合
, 次操作支持插入删除,询问 求出 中有多少字符串以 开头以 结尾。 所有的字符串均以模板串
的 个子串的拼接形式给出。 数据范围:
。
思路分析
先考虑如果字符串是直接给出的怎么做,那么对所有串建 Trie,询问时一个串前缀合法就要求在
同理一个串后缀合法就要在反串 Trie 上在
加上每个串在
因此我们只需要求出每个字符串在 Trie 树上的 dfn 序以及子树最大的 dfn 序。
先考虑 Trie 树上的 dfn 序怎么求,相当于把这些字符串按字典序排序,先二分 lcp, 比较哈希值,求哈希值再二分当前前缀落在哪个子串中,可以
然后考虑子树最大的 dfn 序,设字符串排序后得到
时间复杂度
代码呈现
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=2e5+5,MOD=1e9+21;
mt19937 rnd(time(0));
int bs,pw[MAXN],hc[MAXN],phs[MAXN];
char s[MAXN];
int qhs(int l,int r) { return (phs[r]+1ll*(MOD-pw[r-l+1])*phs[l-1])%MOD; }
int n,m;
struct String {
vector <int> L,R,hv;
vector <ll> siz;
ll len;
void read() {
int k; cin>>k;
L.resize(k),R.resize(k);
for(int i=0;i<k;++i) cin>>L[i]>>R[i];
}
void rev() {
swap(L,R);
for(int &i:L) i=2*n+1-i;
for(int &i:R) i=2*n+1-i;
reverse(L.begin(),L.end());
reverse(R.begin(),R.end());
}
void init() {
int k=L.size(); len=0;
hv.resize(k),siz.resize(k);
for(int i=0;i<k;++i) {
len+=R[i]-L[i]+1,siz[i]=len;
hv[i]=(1ll*(i?hv[i-1]:0)*pw[R[i]-L[i]+1]+qhs(L[i],R[i]))%MOD;
}
}
inline int qhv(ll x) const {
int i=lower_bound(siz.begin(),siz.end(),x)-siz.begin();
if(!i) return qhs(L[i],L[i]+x-1);
x-=siz[i-1];
return (1ll*hv[i-1]*pw[x]+qhs(L[i],L[i]+x-1))%MOD;
}
inline char qc(ll x) const {
int i=lower_bound(siz.begin(),siz.end(),x)-siz.begin();
x-=i?siz[i-1]:0;
return s[L[i]+x-1];
}
};
ll lcp(const String &u,const String &v) {
ll l=1,r=min(u.len,v.len),x=0;
while(l<=r) {
ll mid=(l+r)>>1;
if(u.qhv(mid)==v.qhv(mid)) x=mid,l=mid+1;
else r=mid-1;
}
return x;
}
char op[MAXN];
int del[MAXN],id[MAXN];
void build(String *S,int *st,int *ed) {
int q=0;
for(int i=1;i<=m;++i) if(op[i]!='-') id[++q]=i;
sort(id+1,id+q+1,[&](int x,int y){
ll k=lcp(S[x],S[y]);
if(k==min(S[x].len,S[y].len)) {
if(S[x].len^S[y].len) return S[x].len<S[y].len;
return op[x]=='?'&&op[y]=='+';
}
return S[x].qc(k+1)<S[y].qc(k+1);
});
for(int i=1;i<=q;++i) {
st[id[i]]=i;
const String &str=S[id[i]];
int l=i+1,r=q,k=i,e=str.len,t=str.hv.back();
while(l<=r) {
int mid=(l+r)>>1;
auto chk=[&](const String &v) {
return v.len>=e&&v.qhv(e)==t;
};
if(chk(S[id[mid]])) k=mid,l=mid+1;
else r=mid-1;
}
ed[id[i]]=k;
}
}
String A[MAXN],B[MAXN];
int st1[MAXN],ed1[MAXN],st2[MAXN],ed2[MAXN];
struct opr { int x,l,r,id,k; } a[MAXN*2];
int ans[MAXN];
struct FenwickTree {
int tr[MAXN],st[MAXN],tp,s;
void add(int x,int v) { for(st[++tp]=x;x<=m;x+=x&-x) tr[x]+=v; }
int qry(int x) { for(s=0;x;x&=x-1) s+=tr[x]; return s; }
void init() { while(tp) for(int x=st[tp--];x<=m;x+=x&-x) tr[x]=0; }
} T;
void cdq(int l,int r) {
if(l==r) return ;
int mid=(l+r)>>1;
cdq(l,mid),cdq(mid+1,r);
T.init();
for(int i=mid+1,j=l;i<=r;++i) {
for(;j<=mid&&a[j].x<=a[i].x;++j) if(!a[j].id) T.add(a[j].l,a[j].k);
if(a[i].id) ans[a[i].id]+=a[i].k*(T.qry(a[i].r)-T.qry(a[i].l-1));
}
inplace_merge(a+l,a+mid+1,a+r+1,[&](auto i,auto j){ return i.x<j.x; });
}
signed main() {
bs=rnd()%MOD,pw[0]=1;
for(int i=1;i<MAXN;++i) pw[i]=1ll*pw[i-1]*bs%MOD;
for(int c=0;c<26;++c) hc[c]=rnd()%MOD;
ios::sync_with_stdio(false);
cin>>n>>m>>(s+1);
for(int i=1;i<=n;++i) s[2*n+1-i]=s[i];
for(int i=1;i<=2*n;++i) phs[i]=(1ll*phs[i-1]*bs+hc[s[i]-'a'])%MOD;
for(int i=1;i<=m;++i) {
cin>>op[i];
if(op[i]=='-') cin>>del[i];
else if(op[i]=='?') {
A[i].read(),B[i].read(),B[i].rev();
A[i].init(),B[i].init();
} else {
A[i].read(),B[i]=A[i],B[i].rev();
A[i].init(),B[i].init();
}
}
build(A,st1,ed1),build(B,st2,ed2);
int q=0;
for(int i=1;i<=m;++i) {
if(op[i]=='?') {
a[++q]={st1[i]-1,st2[i],ed2[i],i,-1};
a[++q]={ed1[i],st2[i],ed2[i],i,1};
} else {
int x=(op[i]=='-')?del[i]:i;
a[++q]={st1[x],st2[x],0,0,op[i]=='-'?-1:1};
}
}
cdq(1,q);
for(int i=1;i<=m;++i) if(op[i]=='?') cout<<ans[i]<<"\n";
return 0;
}
Round #31 - 2024.11.14
H. Huge Segment Tree
题目大意
给定
个节点的标准线段树,对每个 求出有多少个 恰好在线段树上拆成 个节点。 数据范围:
。
思路分析
考虑 zkw 线段树的过程,取
然后特判
时间复杂度
代码呈现
#include<bits/stdc++.h>
#define ll long long
using namespace std;
namespace P {
const int MOD=998244353,N=1<<21,G=3;
int rev[N],inv[N],fac[N],ifac[N],w[N<<1];
int ksm(int a,int b=MOD-2) {
int ret=1;
for(;b;a=1ll*a*a%MOD,b=b>>1) if(b&1) ret=1ll*ret*a%MOD;
return ret;
}
void poly_init() {
inv[1]=1;
for(int i=2;i<N;++i) inv[i]=1ll*(MOD-MOD/i)*inv[MOD%i]%MOD;
fac[0]=ifac[0]=1;
for(int i=1;i<N;++i) fac[i]=1ll*fac[i-1]*i%MOD,ifac[i]=1ll*ifac[i-1]*inv[i]%MOD;
for(int k=1;k<=N;k<<=1) {
int x=ksm(G,(MOD-1)/k); w[k]=1;
for(int i=1;i<k;++i) w[i+k]=1ll*x*w[i+k-1]%MOD;
}
}
int plen(int x) { int y=1; for(;y<x;y<<=1); return y; }
void ntt(int *f,bool idft,int n) {
for(int i=0;i<n;++i) {
rev[i]=(rev[i>>1]>>1);
if(i&1) rev[i]|=n>>1;
}
for(int i=0;i<n;++i) if(rev[i]<i) swap(f[i],f[rev[i]]);
for(int k=2,x,y;k<=n;k<<=1) {
for(int i=0;i<n;i+=k) {
for(int j=i;j<i+k/2;++j) {
x=f[j],y=1ll*f[j+k/2]*w[k+j-i]%MOD;
f[j]=(x+y>=MOD)?x+y-MOD:x+y,f[j+k/2]=(x>=y)?x-y:x+MOD-y;
}
}
}
if(idft) {
reverse(f+1,f+n);
for(int i=0,x=ksm(n);i<n;++i) f[i]=1ll*f[i]*x%MOD;
}
}
void poly_mul(const int *f,const int *g,int *h,int n,int m) {
static int a[N],b[N];
for(int i=0;i<n;++i) a[i]=f[i];
for(int i=0;i<m;++i) b[i]=g[i];
int len=plen(n+m-1);
ntt(a,0,len),ntt(b,0,len);
for(int i=0;i<len;++i) h[i]=1ll*a[i]*b[i]%MOD;
ntt(h,1,len);
memset(a,0,sizeof(int)*len);
memset(b,0,sizeof(int)*len);
}
}
const int MAXN=1e6+5,N=1<<21,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 f[MAXN],fac[MAXN],ifac[MAXN],pw[MAXN];
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 a[N],b[N],c[N];
signed main() {
P::poly_init();
for(int i=fac[0]=pw[0]=1;i<MAXN;++i) pw[i]=pw[i-1]*2%MOD,fac[i]=fac[i-1]*i%MOD;
ifac[MAXN-1]=ksm(fac[MAXN-1]);
for(int i=MAXN-1;i;--i) ifac[i-1]=ifac[i]*i%MOD;
ios::sync_with_stdio(false);
int n; cin>>n;
for(int i=1;i<n;++i) a[2*(n-i)]=fac[2*(n-i)]*pw[i-1]%MOD;
for(int i=0;i<=2*n-2;++i) b[2*n-i]=ifac[i];
P::poly_mul(a,b,c,2*n,2*n+1);
for(int i=1;i<=2*n-2;++i) f[i]=ifac[i]*c[i+2*n]%MOD;
for(int i=1;i<=n;++i) f[i]=(f[i]+2*C(n,i))%MOD;
f[1]=(f[1]+1)%MOD;
for(int i=1;i<=2*n-2;++i) cout<<f[i]<<" "; cout<<"\n";
return 0;
}
B. Black or White 2
题目大意
给定
网格,将其中 个格子染黑,最小化恰有两个黑格的 子矩阵个数,给出构造。 数据范围:
。
思路分析
手玩一下发现,一个比较优秀的构造是取一个矩形,然后在四条边界上间隔染色,中间全部染色,此时任何一个
能么一种可能的构造方式如下:
- 给第
行的 列染黑。 - 同时给第
行的 列染黑。 - 同时给第
行的 列染黑。 - 同时给第
行的 列染黑。 - 同时给第
行的 列染黑。
不断重复这个过程,最终会剩余
为了保证能构造,需要调整使得
时间复杂度
代码呈现
#include<bits/stdc++.h>
using namespace std;
const int MAXN=1505;
bool a[MAXN][MAXN];
void solve() {
int n,m,k;
cin>>n>>m>>k;
bool sw=0,rv=0;
if(n<m) swap(n,m),sw=1;
if(k*2>n*m) k=n*m-k,rv=1;
for(int i=1;i<=n;++i) for(int j=1;j<=m;++j) a[i][j]=0;
for(int j=1;j<=m&&k;j+=2) a[1][j]=1,--k;
if(k&1) a[n][m]=1,--k;
for(int i=2;i<=n&&k;++i) for(int j=2-(i&1);j<=m&&k;j+=2) a[i][j]=a[i-1][j]=1,k-=2;
if(sw) swap(n,m);
for(int i=1;i<=n;++i,cout<<"\n") for(int j=1;j<=m;++j) cout<<((sw?a[j][i]:a[i][j])^rv);
}
signed main() {
ios::sync_with_stdio(false);
int T; cin>>T;
while(T--) solve();
return 0;
}
K. Kth Sum
题目大意
给定
,求 的第 小。 数据范围:
。
思路分析
先对
首先如果枚举
但我们发现枚举前
因此我们可以把
单次查询复杂度
时间复杂度
代码呈现
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=5e4+5,MAXS=8e6+5;
ll a[MAXN],b[MAXN],c[MAXN],f[MAXS];
int n,K,B,S;
bool chk(ll v) {
int cnt=0;
for(int i=1;i<=n&&i<=B;++i) {
for(int j=1,k=n;j<=n;++j) {
while(k&&a[i]+b[j]+c[k]>v) --k;
if((cnt+=k)>=K) return false;
}
}
for(int i=B+1,j=S;i<=n;++i) {
while(j&&a[i]+f[j]>v) --j;
if((cnt+=j)>=K) return false;
}
return true;
}
signed main() {
ios::sync_with_stdio(false);
cin>>n>>K,B=sqrt(K/n)+1,S=min(K/B+1ll,1ll*n*n);
for(int i=1;i<=n;++i) cin>>a[i];
for(int i=1;i<=n;++i) cin>>b[i];
for(int i=1;i<=n;++i) cin>>c[i];
sort(a+1,a+n+1),sort(b+1,b+n+1),sort(c+1,c+n+1);
priority_queue <array<ll,2>,vector<array<ll,2>>,greater<array<ll,2>>> Q;
for(int i=1;i<=n;++i) Q.push({b[i]+c[1],1});
for(int i=1;i<=S;++i) {
auto it=Q.top(); Q.pop(),f[i]=it[0];
if(it[1]<n) Q.push({it[0]+c[it[1]+1]-c[it[1]],it[1]+1});
}
ll l=a[1]+b[1]+c[1],r=a[n]+b[n]+c[n],x=l-1;
while(l<=r) {
ll mid=(l+r)>>1;
if(chk(mid)) x=mid,l=mid+1;
else r=mid-1;
}
cout<<x+1<<"\n";
return 0;
}
O. Optimal Train Operation
题目大意
给定
数轴,初始 上有点,可以花 的代价在 上放点,也可以选定两个点 ,花费 的代价覆盖 一次,要求 覆盖 次,求最小代价。 数据范围:
。
思路分析
观察题目,我们发现如果有三个点
考虑 dp,设
这种包含区间最大值的形式的问题,可以考虑 CDQ 分治转移,维护
得到
- 如果
,那么转移为 ,倒序枚举 ,用李超线段树维护。 - 否则转移为
,顺序枚举 ,用李超线段树维护。
建立动态开点李超线段树即可。
时间复杂度
代码呈现
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=5e5+5,V=1e9;
const ll inf=1e18;
struct func {
ll k,b;
inline ll f(const ll &x) const { return x*k+b; }
} F[MAXN];
struct SegmentTree {
int tr[MAXN],ls[MAXN],rs[MAXN],tot;
void init() {
for(int i=1;i<=tot;++i) tr[i]=ls[i]=rs[i]=0;
tot=0;
}
void ins(int i,int l,int r,int &p) {
if(!p) return tr[p=++tot]=i,void();
int mid=(l+r)>>1;
if(F[tr[p]].f(mid)>F[i].f(mid)) swap(tr[p],i);
if(l==r) return ;
if(F[tr[p]].f(l)>F[i].f(l)) ins(i,l,mid,ls[p]);
if(F[tr[p]].f(r)>F[i].f(r)) ins(i,mid+1,r,rs[p]);
}
ll qry(int x,int l,int r,int p) {
if(!p) return inf;
ll w=F[tr[p]].f(x);
if(l==r) return w;
int mid=(l+r)>>1;
if(x<=mid) w=min(w,qry(x,l,mid,ls[p]));
else w=min(w,qry(x,mid+1,r,rs[p]));
return w;
}
} T;
int n,rt;
ll a[MAXN],b[MAXN],dp[MAXN],mx[MAXN];
void cdq(int l,int r) {
if(l==r) return ;
int mid=(l+r)>>1;
cdq(l,mid);
mx[mid]=0,mx[mid+1]=b[mid+1];
for(int i=mid+2;i<=r;++i) mx[i]=max(mx[i-1],b[i]);
for(int i=mid;i>l;--i) mx[i-1]=max(mx[i],b[i]);
T.init(),rt=0;
for(int i=mid+1,j=mid;i<=r;++i) {
for(;j>=l&&mx[j]<=mx[i];--j) F[j]={-j,dp[j]},T.ins(j,0,V,rt);
dp[i]=min(dp[i],T.qry(mx[i],0,V,rt)+i*mx[i]+a[i]);
}
T.init(),rt=0;
for(int i=r,j=l;i>mid;--i) {
for(;j<=mid&&mx[j]>mx[i];++j) F[j]={mx[j],dp[j]-j*mx[j]},T.ins(j,0,V,rt);
dp[i]=min(dp[i],T.qry(i,0,V,rt)+a[i]);
}
cdq(mid+1,r);
}
signed main() {
cin>>n;
F[0]={0,inf};
for(int i=1;i<=n;++i) cin>>b[i];
for(int i=1;i<n;++i) cin>>a[i];
memset(dp,0x3f,sizeof(dp)),dp[0]=0;
cdq(0,n);
cout<<dp[n]<<"\n";
return 0;
}
G. Graph Weighting
题目大意
给定一个
个点 条边的无向图,给每个点赋 权值,对于 求出使得每个生成树边权和 时,边权平方和的最小值。 数据范围:
。
思路分析
先考虑每个生成树边权和
那么可以推出充要条件是图中每个点双联通分量边权相等。
因此对于一个点双联通分量,我们只关心
由于体积是
对于每种
由于使用的物品不超过
把背包下标按
显然
时间复杂度
代码呈现
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=2e5+5;
const ll inf=1e18;
int n,m,K,U;
vector <int> G[MAXN];
int dfn[MAXN],low[MAXN],dcnt,stk[MAXN],tp,scnt,bel[MAXN];
bool ins[MAXN];
int vc[MAXN],ec[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]);
if(low[v]>=dfn[u]) {
++scnt,++vc[scnt];
while(ins[v]) bel[stk[tp]]=scnt,++vc[scnt],ins[stk[tp--]]=false;
}
} else if(ins[v]) low[u]=min(low[u],dfn[v]);
}
}
int S;
vector <int> W[MAXN];
ll dp[MAXN],f[MAXN],g[MAXN],c[MAXN];
void solve(int l,int r,int L,int R) {
if(l>r) return ;
int mid=(l+r)>>1,p=0;
for(int i=max(L,mid-S);i<=R&&i<=mid;++i) {
ll v=f[i]+c[mid-i];
if(v<=g[mid]) g[mid]=v,p=i;
}
solve(l,mid-1,L,p),solve(mid+1,r,p,R);
}
signed main() {
ios::sync_with_stdio(false);
cin>>n>>m>>K>>U;
for(int i=1,u,v;i<=m;++i) cin>>u>>v,G[u].push_back(v),G[v].push_back(u);
tarjan(1);
for(int u=1;u<=n;++u) for(int v:G[u]) if(dfn[v]<dfn[u]) ++ec[bel[u]];
for(int i=1;i<=scnt;++i) W[vc[i]-1].push_back(ec[i]);
for(int i=1;i<=K;++i) dp[i]=inf;
for(int i=1;i<=n;++i) if(W[i].size()) {
priority_queue <array<ll,3>,vector<array<ll,3>>,greater<array<ll,3>>> Q;
for(int w:W[i]) Q.push({w,1,w});
c[0]=0;
for(S=0;Q.size()&&S<K/i;++S) {
auto w=Q.top(); Q.pop();
c[S+1]=c[S]+w[0];
if(w[1]<U) Q.push({w[0]+2*w[2],w[1]+1,w[2]});
}
for(int r=0;r<i;++r) {
int up=(K-r)/i;
for(int j=0;j<=up;++j) f[j]=dp[r+i*j],g[j]=inf;
solve(0,up,0,up);
for(int j=0;j<=up;++j) dp[r+i*j]=g[j];
}
}
for(int i=0;i<=K;++i) cout<<(dp[i]>=inf?-1:dp[i])<<" \n"[i==K];
return 0;
}
*M. Majority and Permutation
题目大意
给定一个奇数构成的集合
,定义一个长度为 的 01 串 是好的,当且仅当 各出现 次,且 , 的众数都是 。 求有多少长度为
的排列 使得存在好的 01 串 ,使得 是好的。 数据范围:
。
思路分析
考虑已知
这可以用堆维护,即用堆维护
分析什么时候这样构造出来的字符串不合法,那么一定是
可以证明这种情况只可能是
那么我们只保留
可以容斥,钦定若干
设被钦定的元素是
因此设
时间复杂度
代码呈现
#include<bits/stdc++.h>
using namespace std;
namespace P {
const int MOD=998244353,N=1<<19,G=3;
int rev[N],inv[N],fac[N],ifac[N],w[N<<1];
int ksm(int a,int b=MOD-2) {
int ret=1;
for(;b;a=1ll*a*a%MOD,b=b>>1) if(b&1) ret=1ll*ret*a%MOD;
return ret;
}
void poly_init() {
inv[1]=1;
for(int i=2;i<N;++i) inv[i]=1ll*(MOD-MOD/i)*inv[MOD%i]%MOD;
fac[0]=ifac[0]=1;
for(int i=1;i<N;++i) fac[i]=1ll*fac[i-1]*i%MOD,ifac[i]=1ll*ifac[i-1]*inv[i]%MOD;
for(int k=1;k<=N;k<<=1) {
int x=ksm(G,(MOD-1)/k); w[k]=1;
for(int i=1;i<k;++i) w[i+k]=1ll*x*w[i+k-1]%MOD;
}
}
int plen(int x) { int y=1; for(;y<x;y<<=1); return y; }
void ntt(int *f,bool idft,int n) {
for(int i=0;i<n;++i) {
rev[i]=(rev[i>>1]>>1);
if(i&1) rev[i]|=n>>1;
}
for(int i=0;i<n;++i) if(rev[i]<i) swap(f[i],f[rev[i]]);
for(int k=2,x,y;k<=n;k<<=1) {
for(int i=0;i<n;i+=k) {
for(int j=i;j<i+k/2;++j) {
x=f[j],y=1ll*f[j+k/2]*w[k+j-i]%MOD;
f[j]=(x+y>=MOD)?x+y-MOD:x+y,f[j+k/2]=(x>=y)?x-y:x+MOD-y;
}
}
}
if(idft) {
reverse(f+1,f+n);
for(int i=0,x=ksm(n);i<n;++i) f[i]=1ll*f[i]*x%MOD;
}
}
void poly_mul(const int *f,const int *g,int *h,int n,int m) {
static int a[N],b[N];
for(int i=0;i<n;++i) a[i]=f[i];
for(int i=0;i<m;++i) b[i]=g[i];
int len=plen(n+m-1);
ntt(a,0,len),ntt(b,0,len);
for(int i=0;i<len;++i) h[i]=1ll*a[i]*b[i]%MOD;
ntt(h,1,len);
memset(a,0,sizeof(int)*len);
memset(b,0,sizeof(int)*len);
}
}
const int N=1<<19,MOD=998244353;
int n,m,dp[N],A[N],B[N],C[N];
bool e[N];
void cdq(int l,int r) {
if(l==r) return ;
int mid=(l+r)>>1;
cdq(l,mid);
for(int i=l;i<=mid;++i) A[i-l]=dp[i];
for(int i=0;i<=r-l;++i) B[i]=P::fac[i];
P::poly_mul(A,B,C,mid-l+1,r-l+1);
for(int i=mid+1;i<=r;++i) if(e[i]) dp[i]=(dp[i]+MOD-C[i-l])%MOD;
cdq(mid+1,r);
}
signed main() {
P::poly_init();
scanf("%d%d",&n,&m),n*=2;
for(int i=1,x;i<=m;++i) scanf("%d",&x),e[x]=true;
for(int i=1;i<=n;++i) e[i]&=e[n-i];
e[n]=true,dp[0]=1,cdq(0,n);
printf("%d\n",(MOD-dp[n])%MOD);
return 0;
}
*P. Priority Queue 3
题目大意
给定长度为
的操作序列, 个 push 操作和 个 pop 操作。 有一个小根堆,每次 push 操作可以选一个
中没被 push 的元素插入堆,pop 操作把最小值弹出并插入 。 给定最终的
,求有多少种合法的 push 方法。 数据范围:
。
思路分析
设
先刻画一组合法的 push 序列,如果我们 push 了一个
因此我们只关心
但是遇到 pop 操作的时候,我们并不知道
那么状态变为
另一个问题是如果操作的元素
考虑这些元素什么时候会产生贡献,当且仅当 pop 掉
可以在转移的时候枚举新的
那么为了处理
由于 pop 掉
讨论转移,设当前这在处理第
-
如果 push 了一个
元素,可能的元素个数为 ,转移 。 -
如果 push 了
, 。 -
如果 push 了其他
中元素,暂不考虑该元素的值, 。 -
如果 pop 了
的元素, 。 -
如果 pop 了
,要求 ,枚举 有 。此时预留的 push 操作数量等于预留的 pop 操作数量,即
,故方案数为 。
按照上述过程 dp。
时间复杂度
代码呈现
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=305,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; }
inline void add(ll &x,const ll &y) { x=(x+y>=MOD)?x+y-MOD:x+y; }
int n,m,a[MAXN];
char str[MAXN*2];
ll fac[MAXN],ifac[MAXN];
ll f[MAXN][MAXN][2],g[MAXN][MAXN][2];
ll A(int x,int y) { return fac[x]*ifac[x-y]%MOD; }
//max not pop = j, now inq = k, [j inq?]
signed main() {
scanf("%d%d%s",&n,&m,str+1);
for(int i=1;i<=m;++i) scanf("%d",&a[i]);
for(int i=fac[0]=ifac[0]=1;i<=n;++i) ifac[i]=ksm(fac[i]=fac[i-1]*i%MOD);
f[m][0][0]=1;
for(int i=1,ci=0,ce=0;i<=n+m;++i) {
memset(g,0,sizeof(g));
if(str[i]=='+') {
for(int j=0;j<=m;++j) for(int k=0;k<=min(i,j);++k) {
for(int o:{0,1}) {
int cnt=(n-a[j])-(m-j)-(ci-ce-k);
if(cnt>0&&ci-ce-k>=0) add(g[j][k][o],f[j][k][o]*cnt%MOD);
add(g[j][k+1][o],f[j][k][o]);
}
add(g[j][k+1][1],f[j][k][0]);
}
++ci;
} else {
for(int j=0;j<=m;++j) {
for(int k=1;k<=min(i,j);++k) for(int o:{0,1}) if(k>1||!o) add(g[j][k-1][o],f[j][k][o]);
int cnt=ce-(m-j);
for(int nj=j-1;~nj;--nj) if(j-nj-1<=cnt) {
add(g[nj][0][0],f[j][1][1]*A(cnt,j-nj-1)%MOD);
}
}
++ce;
}
memcpy(f,g,sizeof(f));
}
printf("%lld\n",f[0][0][0]);
return 0;
}
*F. Flip or Not
题目大意
给定长度为
的 01 串 ,你可以进行 次操作,每次操作依次进行如下步骤:
- 将
变成 ,如果 ,则翻转 。 - 可以选择是否同时翻转
,然后 。 给定
构造一组操作的方案使得该串从 变成 。 数据范围:
。
思路分析
用
定义
第一步操作比较麻烦,首先循环移位等价于
因此假设共操作
设
那么假设
注意到这是一个类似不定方程的问题,可以套用正整数域上的 exgcd,即用多项式除法代替正整数域上的带余除法,类似定义因数倍数关系。
那么推广裴蜀定理得到存在
得到一组特解
设
这个式子并不好维护,但很显然能变形成
用 std::bitset
优化上述维护多项式加减乘除模的过程。
注意到计算多项式除法
时间复杂度
代码呈现
#include<bits/stdc++.h>
using namespace std;
const int N=1e4;
typedef bitset<N+5> poly;
inline int deg(const poly &u) {
for(int i=N;~i;--i) if(u[i]) return i;
return 0;
}
inline poly operator *(const poly &x,const poly &y) {
static poly z,o; z.reset();
for(int i=0;i<N;++i) if(y[i]) o=x,o<<=i,z^=o;
return z;
}
inline array<poly,2> divp(poly x,const poly &y) {
static poly z,o; z.reset();
int dy=deg(y);
for(int i=N-dy;~i;--i) if(x[i+dy]) z.set(i),o=y,o<<=i,x^=o;
return {z,x};
}
inline poly operator /(const poly &x,const poly &y) {
return divp(x,y)[0];
}
inline poly operator %(const poly &x,const poly &y) {
return divp(x,y)[1];
}
inline array<poly,2> exgcd(poly x,poly y) { //px+qy=g
poly p,q; p.set(0);
while(y.any()) {
auto z=divp(x,y);
p^=z[0]*q,swap(p,q);
x=y,y=z[1];
}
return {x,p};
}
void read1(poly &x) {
string s; cin>>s;
for(int i=0;i<(int)s.size();++i) if(s[i]=='1') x.set(i);
}
void read2(poly &x) {
int n; cin>>n;
for(int i;n--;) cin>>i,x.set(i-1);
}
signed main() {
ios::sync_with_stdio(false);
int n;
poly S,T,A,B;
cin>>n,read1(S),read1(T),read2(A),read2(B);
A.flip(0),A.flip(n);
auto rs=exgcd(B,A);
poly g=rs[0],q0=rs[1],gs=S%g,gt=T%g,vs=S*q0%A,vt=T*q0%A;
int d=deg(g);
for(int k=1;k<=1000000;++k) {
gs<<=1; if(gs[d]) gs^=g;
vs<<=1; if(vs[n]) vs^=A;
if(gs!=gt) continue;
poly q=vs^vt;
if(deg(q)-d<k) {
q=q/g;
cout<<k<<"\n"; for(int i=k-1;~i;--i) cout<<(i<n&&q[i]); cout<<"\n";
return 0;
}
}
cout<<"-1\n";
return 0;
}
Round #32 - 2024.11.18
G. Guess One Character
题目大意
给定 01 串
, 次交互询问某个 01 串 在 中作为子串的出现次数,求出 中的任意一个位置。 数据范围:
。
思路分析
将序列看成若干交替的
与
时间复杂度
代码呈现
#include<bits/stdc++.h>
using namespace std;
int q(string s) {
cout<<"1 "<<s<<endl;
int x; cin>>x; return x;
}
void solve() {
int n;
cin>>n;
int sg=q("0")-q("00"),jp=q("01");
cout<<"0 "<<n<<" "<<(jp==sg)<<endl;
int _; cin>>_;
}
signed main() {
int T; cin>>T;
while(T--) solve();
return 0;
}
F. Alternative Platforms
题目大意
给定
,对于每个 ,随机选出一个大小为 的集合 ,求 。 数据范围:
。
思路分析
容斥把所求转成
第三部分可以看成
对
时间复杂度
代码呈现
#include<bits/stdc++.h>
using namespace std;
const int MOD=998244353,N=1<<19,G=3;
int rev[N],inv[N],fac[N],ifac[N],w[N<<1];
int ksm(int a,int b=MOD-2) {
int ret=1;
for(;b;a=1ll*a*a%MOD,b=b>>1) if(b&1) ret=1ll*ret*a%MOD;
return ret;
}
void poly_init() {
inv[1]=1;
for(int i=2;i<N;++i) inv[i]=1ll*(MOD-MOD/i)*inv[MOD%i]%MOD;
fac[0]=ifac[0]=1;
for(int i=1;i<N;++i) fac[i]=1ll*fac[i-1]*i%MOD,ifac[i]=1ll*ifac[i-1]*inv[i]%MOD;
for(int k=1;k<=N;k<<=1) {
int x=ksm(G,(MOD-1)/k); w[k]=1;
for(int i=1;i<k;++i) w[i+k]=1ll*x*w[i+k-1]%MOD;
}
}
int plen(int x) { int y=1; for(;y<x;y<<=1); return y; }
void ntt(int *f,bool idft,int n) {
for(int i=0;i<n;++i) {
rev[i]=(rev[i>>1]>>1);
if(i&1) rev[i]|=n>>1;
}
for(int i=0;i<n;++i) if(rev[i]<i) swap(f[i],f[rev[i]]);
for(int k=2,x,y;k<=n;k<<=1) {
for(int i=0;i<n;i+=k) {
for(int j=i;j<i+k/2;++j) {
x=f[j],y=1ll*f[j+k/2]*w[k+j-i]%MOD;
f[j]=(x+y>=MOD)?x+y-MOD:x+y,f[j+k/2]=(x>=y)?x-y:x+MOD-y;
}
}
}
if(idft) {
reverse(f+1,f+n);
for(int i=0,x=ksm(n);i<n;++i) f[i]=1ll*f[i]*x%MOD;
}
}
void poly_mul(const int *f,const int *g,int *h,int n,int m) {
static int a[N],b[N];
for(int i=0;i<n;++i) a[i]=f[i];
for(int i=0;i<m;++i) b[i]=g[i];
int len=plen(n+m-1);
ntt(a,0,len),ntt(b,0,len);
for(int i=0;i<len;++i) h[i]=1ll*a[i]*b[i]%MOD;
ntt(h,1,len);
memset(a,0,sizeof(int)*len);
memset(b,0,sizeof(int)*len);
}
int C(int x,int y) { return 1ll*fac[x]*ifac[y]%MOD*ifac[x-y]%MOD; }
int n,a[N],b[N],v[N],f[N],g[N],h[N];
signed main() {
poly_init();
scanf("%d",&n);
for(int i=1;i<=n;++i) scanf("%d",&a[i]);
for(int i=1;i<=n;++i) scanf("%d",&b[i]),v[i]=min(a[i],b[i]);
sort(a+1,a+n+1,greater<>()),sort(b+1,b+n+1,greater<>()),sort(v+1,v+n+1,greater<>());
for(int i=1;i<=n;++i) f[i]=1ll*(a[i]+b[i]-v[i])*fac[i-1]%MOD;
for(int i=0;i<=n;++i) g[n-i]=ifac[i];
poly_mul(f,g,h,n+1,n+1);
for(int i=1;i<=n;++i) printf("%lld ",1ll*h[i+n]*ifac[i-1]%MOD*ksm(C(n,i))%MOD); puts("");
return 0;
}
D. Divide OR Conquer
题目大意
给定
,将其划分成若干段子区间,使得从左到右每一段内元素的 OR 和递增。 数据范围:
。
思路分析
显然 dp,
根据经典结论,对于每个
转移为
按
时间复杂度
代码呈现
#include<bits/stdc++.h>
using namespace std;
const int MAXN=2e5+5,V=(1<<30)-1,MOD=998244353;
inline void addv(int &x,const int &y) { x=(x+y>=MOD)?x+y-MOD:x+y; }
int n,a[MAXN],st[MAXN][20],rt[MAXN];
int bit(int x) { return 1<<x; }
int qry(int l,int r) {
int k=__lg(r-l+1);
return st[l][k]|st[r-bit(k)+1][k];
}
struct FenwickTree {
int tr[MAXN],s;
void add(int x,int v) { for(++x;x<=n+1;x+=x&-x) addv(tr[x],v); }
int qry(int x) { for(s=0;x;x&=x-1) addv(s,tr[x]); return s; }
int qry(int l,int r) { return (qry(r+1)+MOD-qry(l))%MOD; }
} T;
struct seg {
int l,r,x,v;
};
int pre(int k,int R) {
int l=1,r=k,p=k,w=qry(k,R);
while(l<=r) {
int mid=(l+r)>>1;
if(qry(mid,R)==w) p=mid,r=mid-1;
else l=mid+1;
}
return p;
}
signed main() {
ios::sync_with_stdio(false);
cin>>n;
for(int i=1;i<=n;++i) cin>>a[i],st[i][0]=a[i];
for(int k=1;k<20;++k) for(int i=1;i+bit(k)-1<=n;++i) {
st[i][k]=st[i][k-1]|st[i+bit(k-1)][k-1];
}
vector <seg> Q;
Q.push_back({0,0,0,0});
for(int i=1;i<=n;++i) {
for(int j=i;j;) {
int k=pre(j,i);
Q.push_back({k-1,j-1,i,qry(j,i)}),j=k-1;
}
}
sort(Q.begin(),Q.end(),[&](auto i,auto j){
return i.v^j.v?i.v<j.v:i.x<j.x;
});
for(auto q:Q) {
int val=q.x?T.qry(q.l,q.r):1;
T.add(q.x,val);
}
printf("%d\n",T.qry(n,n));
return 0;
}
M. Royal Flush
题目大意
给定
种花色,每种花色 张牌,初始你有五张牌,每轮你可以弃置手中的若干张牌,抽取等量的牌,目标是让你的手牌为同一种花色的 ,求最优策略下的期望轮数。 数据范围:
。
思路分析
直接爆搜,进行一定的剪枝:
-
点数不为
的牌拿到就会立刻弃置,只关心这种牌的种数。 -
注意到某种花色中的
如果被弃置了一张,剩余的牌此后拿到也会立刻弃置。因此每种花色只有两种状态:不可能凑出同花顺,或还剩
张牌在牌堆,手上有 张牌。
转移的时候搜索求出每种牌弃置了几张,以及每种牌抽到了几张,可以很快得出结果。
回答答案时打表,时间复杂度
代码呈现
#include<bits/stdc++.h>
#define ll long long
#define ld long double
using namespace std;
const int n=4; //change this
const ld inf=1e18;
typedef array<int,n> info;
map <pair<int,info>,ld> DP;
ll C[60][60];
ld f(int m,info a) {
if(*max_element(a.begin(),a.end())==-1) return inf;
if(*max_element(a.begin(),a.end())==5) return 0;
if(DP.count({m,a})) return DP[{m,a}];
int o=5,x[n+5],y[n+5];
for(int i=0;i<n;++i) if(~a[i]) o-=a[i];
ld S=inf;
for(int s=0;s<(1<<n);++s) {
bool ok=1;
for(int i=0;i<n;++i) if(s>>i&1) ok&=a[i]>0;
if(!ok) continue;
ld val=0; int cnt=o;
y[n]=m;
for(int i=0;i<n;++i) if(~a[i]) {
if(s>>i&1) y[i]=0,cnt+=a[i];
else y[i]=5-a[i],y[n]-=y[i];
} else y[i]=0;
if(!cnt||cnt>m) continue;
function<void(int,int)> dfs=[&](int p,int r) {
if(p==n) {
if(r>y[n]) return ;
x[n]=r;
ld pr=1;
info b; b.fill(0);
for(int i=0;i<n;++i) if(~a[i]) {
if(s>>i&1) b[i]=-1;
else b[i]=x[i]+a[i];
} else b[i]=-1;
for(int i=0;i<=n;++i) {
pr*=C[y[i]][x[i]];
}
pr/=C[m][cnt];
val+=pr*f(m-cnt,b);
return ;
}
for(x[p]=0;x[p]<=min(y[p],r);++x[p]) dfs(p+1,r-x[p]);
return ;
};
dfs(0,cnt);
S=min(S,val);
}
return DP[{m,a}]=S+1;
}
signed main() {
for(int i=0;i<60;++i) for(int j=C[i][0]=1;j<=i;++j) C[i][j]=C[i-1][j]+C[i-1][j-1];
int x[n+5],y[n+5];
for(int i=0;i<n;++i) y[i]=5;
y[n]=8*n;
ld val=0;
function<void(int,int)> dfs=[&](int p,int r) {
if(p==n) {
if(r>y[n]) return ;
x[n]=r;
ld pr=1;
info b; b.fill(0);
for(int i=0;i<n;++i) b[i]=x[i];
for(int i=0;i<=n;++i) {
pr*=C[y[i]][x[i]];
}
pr/=C[n*13][5];
val+=pr*f(n*13-5,b);
return ;
}
for(x[p]=0;x[p]<=min(y[p],r);++x[p]) dfs(p+1,r-x[p]);
return ;
};
dfs(0,5);
printf("%.20Lf\n",val);
return 0;
}
*H. Galactic Council
题目大意
给定
个人, 轮,每轮可以投票给当前得票非最多的人,第 轮投票给 有 收益。 已知每轮投票后得票最多的人,构造方案最大化收益。
数据范围:
。
思路分析
由于我们不会给当前最多票的人投票,因此每个时刻票数最多的人的得票数已知,即每个时刻每个人的票数上限已知。
可以用网络流描述,
由于我们要当前钦定票数最多的人的得票数,因此要做上下界最大费用最大流。
时间复杂度
代码呈现
#include<bits/stdc++.h>
using namespace std;
const int inf=1e9;
namespace F {
const int MAXV=5005,MAXE=3e5+5;
struct Edge { int v,e,f,w; } G[MAXE];
int S,T,ec=1,hd[MAXV],dis[MAXV],pre[MAXV];
bool inq[MAXV];
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=55,MAXV=5005;
int n,m,s,t,a[MAXN],cl[MAXN],w[MAXN][MAXN],tot,id[MAXN][MAXN],deg[MAXV],e[MAXN][MAXN];
int link(int u,int v,int l,int r,int c) {
deg[u]-=l,deg[v]+=l; if(l<r) F::link(u,v,r-l,c);
return F::ec;
}
signed main() {
scanf("%d%d",&n,&m),s=++tot,t=++tot;
for(int i=1;i<=m;++i) scanf("%d",&cl[i]),a[i]=a[i-1]+(cl[i]>cl[i-1]);
for(int i=1;i<=n;++i) {
for(int j=1;j<=m;++j) id[i][j]=++tot;
id[i][m+1]=t;
}
for(int i=1;i<=n;++i) for(int j=1;j<=m;++j) {
if(i==cl[j]) link(id[i][j],id[i][j+1],a[j],a[j],0);
else link(id[i][j],id[i][j+1],0,a[j]-(i<cl[j]),0);
}
for(int i=1;i<=n;++i) for(int j=1;j<=m;++j) scanf("%d",&w[i][j]);
for(int j=1;j<=m;++j) {
int nw=++tot; link(s,nw,1,1,0);
for(int i=1;i<=n;++i) if(i!=cl[j-1]) e[i][j]=link(nw,id[i][j],0,1,-w[i][j]);
}
int vs=F::S=tot+1,vt=F::T=tot+2,trg=0;
for(int i=1;i<=tot;++i) {
if(deg[i]<0) F::link(i,vt,-deg[i],0);
else if(deg[i]) trg+=deg[i],F::link(vs,i,deg[i],0);
}
F::link(t,s,inf,0);
if(F::ssp()[0]!=trg) return puts("-1"),0;
for(int i=1;i<=tot+2;++i) F::hd[i]=F::G[F::hd[i]].e;
F::S=s,F::T=t,F::ssp();
for(int j=1;j<=m;++j) {
for(int i=1;i<=n;++i) if(F::G[e[i][j]].f) printf("%d ",i);
}
puts("");
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· C#/.NET/.NET Core优秀项目和框架2025年2月简报
· Manus爆火,是硬核还是营销?
· 一文读懂知识蒸馏
· 终于写完轮子一部分:tcp代理 了,记录一下
2023-12-10 JOISC2017 题解