今日小测,搜了一下发现是GDSOI2018模拟4.19
sequence
我的方法和题解不太一样:
对于每个差= f ( a ) f(a) f(a)的有序点对 ( i , j ) ( i < j ) (i,j)(i<j) (i,j)(i<j)连边 i → j i\to j i→j,答案就是二分图最大匹配数(等价于最小点覆盖数)
连边是 n 2 n^2 n2的,发现可以栈贪心匹配不用实际跑二分图于是复杂度就 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
网络流题终究做不出来。。。
这道题用到了 最大权闭合子图 和 最小割“切糕”模型 的思想:
发现对于 D A G DAG DAG上的每条路径,必然是不选->选->不选。
拆点
S
→
i
→
i
′
→
T
S\to i\to i'\to T
S→i→i′→T,割三条边分别表示这个点不同的状态(在选择路径前/中/后)。
对于
D
A
G
DAG
DAG中的所有边
(
u
→
v
)
(u\to v)
(u→v),需要强制
u
u
u的状态
≤
v
\leq \ v
≤ v的状态,所以连边
(
u
,
v
,
i
n
f
)
,
(
u
′
,
v
′
,
i
n
f
)
(u,v,inf),(u',v',inf)
(u,v,inf),(u′,v′,inf)(“切糕”模型,强制相对顺序)。
对于每个点三条割边贡献的处理,采用最大权闭合子图的思想——若 x i > 0 x_i>0 xi>0,则连边 ( S , i , i , x i ) , ( i ′ , T , x i ) (S,i,i,x_i),(i',T,x_i) (S,i,i,xi),(i′,T,xi),否则连边 ( i , i ′ , − x i ) (i,i',-x_i) (i,i′,−xi)。
a n s = ∑ a i > 0 a i − ans=\sum\limits_{a_i>0}a_i- ans=ai>0∑ai−最小割
#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 s长度为 n n n,下标从1开始( s [ 1... n ] s[1...n] s[1...n])
首先解决字符串合法的问题:
每次删去任意一对相邻的相同字符,设
s
s
s操作完毕后变为
f
(
s
)
f(s)
f(s),若
f
(
s
)
f(s)
f(s)为空串,则
s
s
s合法。
于是可以栈维护 s s s的 f ( s ) f(s) f(s),加入和删除一个字符都是 O ( 1 ) O(1) O(1)的(若加入的字符和上一个字符相同,则一起删去)。
字符集 ≤ 3 \leq 3 ≤3,可以枚举交换的两个字符 ( a , b ) (a,b) (a,b):
考虑分治,处理到区间 ( l , r ) (l,r) (l,r)时,分别枚举 a a a的位置 i , i ∈ [ l , m i d ] i,i\in[l,mid] i,i∈[l,mid]中, b b b的位置 j , j ∈ ( m i d , r ] j,j\in(mid,r] j,j∈(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'[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′[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 ′ [ 1 , m i d ] ) = r e v e r s e ( f ( s ′ [ m i d + 1 , n ] ) ) f(s'[1,mid])=reverse(f(s'[mid+1,n])) f(s′[1,mid])=reverse(f(s′[mid+1,n]))。
考虑同时处理出 f ( s ) f(s) f(s)前缀后缀的哈希,在上述 a → b a\to b a→b过程中,相当于二分查找 f ( s [ 1 , i − 1 ] ) 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])) reverse(f(s[i+1,mid]))的最长公共后缀。
复杂度 O ( n log 2 n ) O(n\log ^2 n) O(nlog2n)
细节:
- 枚举 i i i的时候处理出所有情况的哈希值装在桶里,枚举 j j j时用翻转串的哈希值加上对应的桶的值即可。
- 字符集太小,要用双哈希,而且膜了std发现双哈希还不够,要用 1 0 1 8 10^18 1018级别的模数!还要高精乘!
- 代码技巧丰富,可以细细品味一下。
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了吗?”