【TEST190325】GDSOI2018模拟4.19 简要题解

版权声明:欢迎转载(请附带原链接)ヾ(๑╹◡╹)ノ https://blog.csdn.net/corsica6/article/details/88803699

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


sequence

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

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

连边是 n 2 n^2 的,发现可以栈贪心匹配不用实际跑二分图于是复杂度就 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

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

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

发现对于 D A G DAG 上的每条路径,必然是不选->选->不选。

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

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

a n s = a i &gt; 0 a i ans=\sum\limits_{a_i&gt;0}a_i- 最小割

#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

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

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

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

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

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

f ( s [ 1 , m i d ] ) = f ( f ( s [ 1 , i 1 ] ) + b + f ( s [ i + 1 , m i d ] ) ) f ( s [ m i d + 1 , n ] ) = f ( f ( s [ m i d + 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 , m i d ] ) = r e v e r s e ( f ( s [ m i d + 1 , n ] ) ) f(s&#x27;[1,mid])=reverse(f(s&#x27;[mid+1,n]))

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

复杂度 O ( n log 2 n ) O(n\log ^2 n)

细节:

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

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了吗?”

猜你喜欢

转载自blog.csdn.net/corsica6/article/details/88803699