【TEST190325】GDSOI2018模拟4.19 简要题解

本文详细解析了GDSOI2018模拟赛中的一道题目,涉及序列问题的二分图最大匹配、排列问题的网络流模型以及字符串合法性的判断与哈希技术。通过分治和动态规划方法,讨论了如何在O(n log^2 n)的时间复杂度内解决字符串交换问题,强调了掌握并灵活运用各种算法模型的重要性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

今日小测,搜了一下发现是GDSOI2018模拟4.19


sequence

我的方法和题解不太一样:

对于每个差=f(a)f(a)f(a)的有序点对(i,j)(i&lt;j)(i,j)(i&lt;j)(i,j)(i<j)连边i→ji\to jij,答案就是二分图最大匹配数(等价于最小点覆盖数)

连边是n2n^2n2的,发现可以栈贪心匹配不用实际跑二分图于是复杂度就O(n)O(n)O(n)了。

正确性玄学。

#include<bits/stdc++.h>
#define pb push_back
using namespace std;
const int N=1e6+10,M=1e7+10,inf=2e9;
typedef long long ll;
typedef double db;

int n,a[N],ans;

char buf[(1<<15)];int p1=0,p2=0;
inline char gc()
{
	if(p1==p2) p1=0,p2=fread(buf,1,(1<<15),stdin);
	return (p1==p2)?EOF:buf[p1++];
}
char cp;
template<class T>inline void rd(T &x)
{
	cp=gc();x=0;int f=0;
	for(;!isdigit(cp);cp=gc()) if(cp=='-') f=1;
	for(;isdigit(cp);cp=gc()) x=x*10+(cp^48);
	if(f) x=-x;
}

namespace Greedy{
	int cot,v[N],mn[N],mx,num,bel[N];
	vector<int>hv[N];
	bool typ[N];
	
	void sol()
	{
		int i,j,k,dlt=0,ss,rr;mn[0]=n+1;mx=0;
		for(i=1;i<=n;++i) v[i]=a[i];
		sort(v+1,v+n+1);num=unique(v+1,v+n+1)-v-1;
		for(i=1;i<=n;++i) a[i]=lower_bound(v+1,v+n+1,a[i])-v;
		for(i=1;i<=n;++i) mn[i]=min(mn[i-1],a[i]);
		for(i=n;i>1;--i){mx=max(mx,a[i]);dlt=max(dlt,v[mx]-v[mn[i-1]]);}
		for(mx=0,i=n;i>1;--i){
			mx=max(mx,a[i]);
			if(dlt==v[mx]-v[mn[i-1]] && (!bel[mx])){
				bel[mx]=bel[mn[i-1]]=++cot;typ[mx]=true;
			}
		}
		for(i=1;i<=n;++i) if(bel[a[i]]) hv[bel[a[i]]].pb(typ[a[i]]);
		for(j=1;j<=cot;++j){
			k=hv[j].size();mx=ss=rr=0;
			for(i=dlt=0;i<k;++i){
				if(hv[j][i]) {ss++;if(ss>0) ss=0,mx++;rr++;}
				else ss--;
			}
			ans+=(rr-mx);
		}
		printf("%d",ans);
    }
	
} 
int main(){
	freopen("sequence.in","r",stdin);
	freopen("sequence.out","w",stdout);
	int i,j,x,y;
    rd(n);
    for(i=1;i<=n;++i) rd(a[i]);
	Greedy::sol();
    fclose(stdin);fclose(stdout);
	return 0;
}

*permutation

网络流题终究做不出来。。。

这道题用到了 最大权闭合子图 和 最小割“切糕”模型 的思想:

发现对于DAGDAGDAG上的每条路径,必然是不选->选->不选。

拆点S→i→i′→TS\to i\to i&#x27;\to TSiiT,割三条边分别表示这个点不同的状态(在选择路径前/中/后)。
对于DAGDAGDAG中的所有边(u→v)(u\to v)(uv),需要强制uuu的状态≤ v\leq \ v v的状态,所以连边(u,v,inf),(u′,v′,inf)(u,v,inf),(u&#x27;,v&#x27;,inf)(u,v,inf),(u,v,inf)(“切糕”模型,强制相对顺序)。

对于每个点三条割边贡献的处理,采用最大权闭合子图的思想——若xi&gt;0x_i&gt;0xi>0,则连边(S,i,i,xi),(i′,T,xi)(S,i,i,x_i),(i&#x27;,T,x_i)(S,i,i,xi),(i,T,xi),否则连边(i,i′,−xi)(i,i&#x27;,-x_i)(i,i,xi)

ans=∑ai&gt;0ai−ans=\sum\limits_{a_i&gt;0}a_i-ans=ai>0ai最小割

#include<bits/stdc++.h>
#define pb push_back
#define gc getchar
using namespace std;
const int N=1010,M=50040,inf=0x7f7f7f7f;
typedef long long ll;
typedef double db;

int n,m,a[N],s[N],S,T,d[N],dep[N],ans;
int head[N],to[M],nxt[M],w[M],tot=1;

inline void lk(int u,int v,int vv)
{
    to[++tot]=v;nxt[tot]=head[u];head[u]=tot;w[tot]=vv;
	to[++tot]=u;nxt[tot]=head[v];head[v]=tot;w[tot]=0;
}
	
queue<int>que;
inline bool bfs()
{
	memset(dep,0xff,sizeof(int)*(T+2));
	dep[S]=0;int i,j,x;que.push(S);
	for(;!que.empty();){
		x=que.front();que.pop();
		for(i=head[x];i;i=nxt[i]){
			j=to[i];if((!w[i])|| (dep[j]!=-1)) continue;
			dep[j]=dep[x]+1;que.push(j);
		}
	}
	return (dep[T]!=-1);
}
	
int dfs(int x,int f)
{
	if(x==T) return f;
	int i,j,ss=0,res;
	for(i=head[x];i;i=nxt[i]){
		j=to[i];if((!w[i])||(dep[j]!=dep[x]+1)) continue;
		res=dfs(j,min(f-ss,w[i]));if(!res) continue;
		w[i]-=res;w[i^1]+=res;ss+=res;if(ss==f) return ss;
	}
	if(!ss) dep[x]=-1;
	return ss;
}

char cp;
template<class T>inline void rd(T &x)
{
	cp=gc();x=0;int f=0;
	for(;!isdigit(cp);cp=gc()) if(cp=='-') f=1;
	for(;isdigit(cp);cp=gc()) x=x*10+(cp^48);
	if(f) x=-x;
}

int main(){
	freopen("permutation.in","r",stdin);
	freopen("permutation.out","w",stdout);
	int i,j,x,y;
    rd(n);rd(m);T=n+n+1; 
    for(i=1;i<=n;++i){
    	rd(x);
    	if(x>0) lk(S,i,x),lk(i+n,T,x),ans+=x;
    	else lk(i,i+n,-x);
	}
    for(i=1;i<=m;++i){
    	rd(x);rd(y);lk(x,y,inf);lk(x+n,y+n,inf);
	}
	for(;bfs();) ans-=dfs(S,inf);
	printf("%d",ans);
    fclose(stdin);fclose(stdout);
	return 0;
}


*swap

设原串sss长度为nnn,下标从1开始(s[1...n]s[1...n]s[1...n])

首先解决字符串合法的问题:
每次删去任意一对相邻的相同字符,设sss操作完毕后变为f(s)f(s)f(s),若f(s)f(s)f(s)为空串,则sss合法。

于是可以栈维护sssf(s)f(s)f(s),加入和删除一个字符都是O(1)O(1)O(1)的(若加入的字符和上一个字符相同,则一起删去)。

字符集≤3\leq 33,可以枚举交换的两个字符(a,b)(a,b)(a,b)

考虑分治,处理到区间(l,r)(l,r)(l,r)时,分别枚举aaa的位置i,i∈[l,mid]i,i\in[l,mid]i,i[l,mid]中,bbb的位置j,j∈(mid,r]j,j\in(mid,r]j,j(mid,r]中:

f(s′[1,mid])=f(f(s[1,i−1])+b+f(s[i+1,mid]))f(s′[mid+1,n])=f(f(s[mid+1,j−1])+a+f(s[j+1,n]))f(s&#x27;[1,mid])=f(f(s[1,i-1])+b+f(s[i+1,mid]))\\ f(s&#x27;[mid+1,n])=f(f(s[mid+1,j-1])+a+f(s[j+1,n]))f(s[1,mid])=f(f(s[1,i1])+b+f(s[i+1,mid]))f(s[mid+1,n])=f(f(s[mid+1,j1])+a+f(s[j+1,n]))

要求合并后合法,故f(s′[1,mid])=reverse(f(s′[mid+1,n]))f(s&#x27;[1,mid])=reverse(f(s&#x27;[mid+1,n]))f(s[1,mid])=reverse(f(s[mid+1,n]))

考虑同时处理出f(s)f(s)f(s)前缀后缀的哈希,在上述a→ba\to bab过程中,相当于二分查找f(s[1,i−1])f(s[1,i-1])f(s[1,i1])reverse(f(s[i+1,mid]))reverse(f(s[i+1,mid]))reverse(f(s[i+1,mid]))的最长公共后缀。

复杂度O(nlog⁡2n)O(n\log ^2 n)O(nlog2n)

细节:

  • 枚举iii的时候处理出所有情况的哈希值装在桶里,枚举jjj时用翻转串的哈希值加上对应的桶的值即可。
  • 字符集太小,要用双哈希,而且膜了std发现双哈希还不够,要用101810^181018级别的模数!还要高精乘!
  • 代码技巧丰富,可以细细品味一下。

STD Orz

#include<bits/stdc++.h>
typedef long long ll;
#define pll pair<ll,ll>
#define mkp make_pair
#define fi first
#define sc second
#define pb push_back
const int N=1e5+10;
const ll p1=1000000000000000003ll,p2=1000000000000000009ll;
using namespace std;

int n;ll ans;
char v[N];pll pw[N];

inline ll mul(ll a,ll b,ll p)
{
	ll d=(a*b-(ll)((long double)a/p*b+(1e-8))*p);
	return d<0?d+p:d;
}
inline ll ad(ll x,ll y,ll p){x+=y;return x>=p?x-p:x;}
inline ll dc(ll x,ll y,ll p){x-=y;return x<0?x+p:x;}
inline pll operator *(pll a,ll b){return mkp(a.fi*b%p1,a.sc*b%p2);}
inline pll operator +(pll a,ll b){return mkp(ad(a.fi,b,p1),ad(a.sc,b,p2));}
inline pll operator +(pll a,pll b){return mkp(ad(a.fi,b.fi,p1),ad(a.sc,b.sc,p2));}
inline pll operator -(pll a,pll b){return mkp(dc(a.fi,b.fi,p1),dc(a.sc,b.sc,p2));}
inline pll operator *(pll a,pll b){return mkp(mul(a.fi,b.fi,p1),mul(a.sc,b.sc,p2));} 

struct HS{
	int pos,sz;
	vector<char>s;
	vector<pll>a,b;
	inline void init(int x)
	{
		pos=x;s.resize(1);a.resize(1);b.resize(1);
		sz=0;s[0]=0;a[0]=b[0]=mkp(0,0);
	}
	inline void ins(char c)
	{
		if(s[sz]==c) {s.resize(sz);a.resize(sz);b.resize(sz);sz--;}
		else{s.pb(c);a.pb((a[sz]*7)+(c-'a'+1));b.pb((b[sz]+(pw[sz]*(c-'a'+1))));sz++;}
	} 
	//正向加
	//反向加2次可以抵消(l->r + r->l = empty) 所以没有另外的撤回操作 
	inline void nt(int x)
	{
		for(;pos>x;) ins(v[pos--]); 
		for(;pos<x;) ins(v[++pos]);
	}
	inline void tn(int x)
	{
		for(;pos>x;) ins(v[--pos]);
		for(;pos<x;) ins(v[pos++]);
	}
	inline pll fd(int x){return a[sz]-(pw[x]*a[sz-x]);}
}a,b,c;

inline int cal(int x,int y){return x*2+y-(y>x);}
inline pll meg(HS a,char c,HS b)
{
	a.ins(c);int l=0,r=min(a.sz,b.sz),sim=0,mid;
    for(;l<=r;){mid=(l+r)>>1;(a.fd(mid)==b.fd(mid))?(l=(sim=mid)+1):(r=mid-1);}
    return (pw[b.sz-sim]*a.a[a.sz-sim])+b.b[b.sz-sim]; 
}

map<pll,int>w[6];
void sol(int l,int r)
{
	if(l==r) return;
	int i,j,mid=(l+r)>>1;
	a.nt(mid-1);c.init(mid+1);
	//注意c是倒着加的 
	for(i=mid;;){
		for(j=0;j<3;++j) if(v[i]!='a'+j)
		    w[cal(v[i]-'a',j)][meg(a,'a'+j,c)]++; 
		if(i==l) break; 
		i--;a.nt(i-1);c.tn(i+1); 
	}
	b.tn(mid+2);c.init(mid);
	for(i=mid+1;;){
		for(j=0;j<3;++j) if(v[i]!='a'+j)
		    ans+=w[cal(j,v[i]-'a')][meg(b,'a'+j,c)];//reverse
		if(i==r) break;
		i++;b.tn(i+1);c.nt(i-1); 
	}
	for(i=0;i<6;++i) w[i].clear();
	sol(l,mid);sol(mid+1,r);
}

int main(){
	freopen("swap.in","r",stdin);
	freopen("swap.out","w",stdout);
    pw[0]=mkp(1,1);
	scanf("%s",v+1);n=strlen(v+1);
	for(int i=1;i<=n;++i) pw[i]=pw[i-1]*7;//*13/17 要用快速乘,会T 
	a.init(0);b.init(n+1);
	sol(1,n);
	printf("%lld",ans);
	return 0;
}

总结

T1切的太慢
T2网络流永远都不会建模,给跪了,需要非常熟练地掌握和运用各种模型
T3没有时间想了,代码十分巧妙,我的哈希姿势不熟练估计也打不出来(点对分治——很经典的套路,很妙)

我的思维还是有问题,举一反三能力太差,或者是对模型理解不够深入
STO llppdd:“T2是真的傻”“会最大权闭合子图不是就秒想T2了吗?”

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值