IOI2020 集训队作业 Part 2

CF627E Orchestra

CF639E Bear and Paradox

CF639F Bear and Chemistry

CF666D Chain Reaction

CF666E Forensic Examination

CF671D Roads in Yusland

CF671E Organizing a Race

CF643D Bearish Fanpages

CF643F Bears and Juice

CF643G Choosing Ads

题解内容是这里的第一个。

#include<bits/stdc++.h>
using namespace std;
const int maxn=150010;
#define lson o<<1,l,mid
#define rson o<<1|1,mid+1,r
#define FOR(i,a,b) for(int i=(a);i<=(b);i++)
#define ROF(i,a,b) for(int i=(a);i>=(b);i--)
#define MEM(x,v) memset(x,v,sizeof(x))
inline int read(){
    int x=0,f=0;char ch=getchar();
    while(ch<'0' || ch>'9') f|=ch=='-',ch=getchar();
    while(ch>='0' && ch<='9') x=x*10+ch-'0',ch=getchar();
    return f?-x:x;
}
int n,q,p,cov[maxn*4],a[maxn];
struct fuck{
	int x[5],cnt[5];
	fuck(){MEM(x,0);MEM(cnt,0);}
	void insert(int x_,int cnt_){
		FOR(i,0,p-1) if(x_==x[i]) return void(cnt[i]+=cnt_);
		FOR(i,0,p-1) if(!x[i]) return void((x[i]=x_,cnt[i]=cnt_));
		int mn=0;
		FOR(i,1,p-1) if(x[i] && cnt[i]<cnt[mn]) mn=i;
		if(cnt_<cnt[mn]) FOR(i,0,p-1) cnt[i]-=cnt_;
		else{
			int tmp=cnt[mn];
			x[mn]=x_;cnt[mn]=cnt_;
			FOR(i,0,p-1) cnt[i]-=tmp;
		}
	}
	fuck operator+(fuck f)const{
		fuck ans=*this;
		FOR(i,0,p-1) if(f.x[i]) ans.insert(f.x[i],f.cnt[i]);
		return ans;
	}
}seg[maxn*4];
inline void cover(int o,int l,int r,int v){
	FOR(i,0,p-1) seg[o].x[i]=seg[o].cnt[i]=0;
	seg[o].x[0]=v;seg[o].cnt[0]=r-l+1;
	cov[o]=v;
}
inline void pushdown(int o,int l,int r){
	if(cov[o]){
		int mid=(l+r)>>1;
		cover(lson,cov[o]);
		cover(rson,cov[o]);
		cov[o]=0;
	}
}
void build(int o,int l,int r){
	if(l==r) return void((seg[o].x[0]=a[l],seg[o].cnt[0]=1));
	int mid=(l+r)>>1;
	build(lson);build(rson);
	seg[o]=seg[o<<1]+seg[o<<1|1];
//	printf("[%d,%d]:\n",l,r);
//	FOR(i,0,p-1) printf("%d %d\n",seg[o].x[i],seg[o].cnt[i]);
}
void update(int o,int l,int r,int ql,int qr,int v){
	if(l>=ql && r<=qr) return cover(o,l,r,v);
	pushdown(o,l,r);
	int mid=(l+r)>>1;
	if(mid>=ql) update(lson,ql,qr,v);
	if(mid<qr) update(rson,ql,qr,v);
	seg[o]=seg[o<<1]+seg[o<<1|1];
}
fuck query(int o,int l,int r,int ql,int qr){
	if(l>=ql && r<=qr) return seg[o];
	pushdown(o,l,r);
	int mid=(l+r)>>1;
	if(mid<ql) return query(rson,ql,qr);
	if(mid>=qr) return query(lson,ql,qr);
	return query(lson,ql,qr)+query(rson,ql,qr);
}
int main(){
	n=read();q=read();p=100/read();
	FOR(i,1,n) a[i]=read();
	build(1,1,n);
	while(q--){
		int op=read(),x=read(),y=read();
		if(op==1) update(1,1,n,x,y,read());
		else{
			fuck ans=query(1,1,n,x,y);
			printf("%d ",p);
			FOR(i,0,p-1) printf("%d ",ans.x[i]);
			puts("");
		}
	}
}

CF679E Bear and Bad Powers of 42

CF685C Optimal Point

CF696F ...Dary!

CF698D Limak and Shooting Points

考虑对于每一个怪判断有没有可能被打死。

假如 \(k\) 个射击点的访问顺序是 \(p_1,p_2,\dots,p_k\)。不妨钦定是 \(p_k\) 最后射死这只怪物,\(p_{k-1}\) 用来射挡住它的最后一个怪物,\(p_{k-2}\) 用来……自行体会。

这样肯定枚举到了所有可能有用的方案。

问题就很简单了,对于每个怪物大暴力,然后瞎搞。

时间复杂度看实现,我的实现是 \(O(n^2k+nk^2\times k!)\),完全跑不满,跑得飞快。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int> PII;
const int maxk=11,maxn=1111;
#define MP make_pair
#define lson o<<1,l,mid
#define rson o<<1|1,mid+1,r
#define FOR(i,a,b) for(int i=(a);i<=(b);i++)
#define ROF(i,a,b) for(int i=(a);i>=(b);i--)
#define MEM(x,v) memset(x,v,sizeof(x))
inline int read(){
    int x=0,f=0;char ch=getchar();
    while(ch<'0' || ch>'9') f|=ch=='-',ch=getchar();
    while(ch>='0' && ch<='9') x=x*10+ch-'0',ch=getchar();
    return f?-x:x;
}
int k,n,sx[maxk],sy[maxk],tx[maxn],ty[maxn],p[maxn],piv,ans,seq[maxk],cur,tmp[maxk],tl;
vector<int> v[maxk][maxn];
bool used[maxk],vis[maxn];
inline ll dis(int x1,int y1,int x2,int y2){
	int xd=x1-x2,yd=y1-y2;
	return 1ll*xd*xd+1ll*yd*yd;
}
inline bool cmp(int x,int y){
	return dis(sx[piv],sy[piv],tx[x],ty[x])>dis(sx[piv],sy[piv],tx[y],ty[y]);
}
inline bool on(int x,int y,int x1,int y1,int x2,int y2){
	int xd1=x1-x,yd1=y1-y,xd2=x2-x,yd2=y2-y;
	if(1ll*xd1*yd2!=1ll*yd1*xd2) return false;
	if(x<min(x1,x2) || x>max(x1,x2)) return false;
	if(y<min(y1,y2) || y>max(y1,y2)) return false;
	return true; 
}
bool check(int to){
	int now=--cur;
	FOR(i,0,(int)v[seq[now]][to].size()-1){
		int x=v[seq[now]][to][i];
		if(vis[x]) continue;
		if(!check(x) || !cur) return false;
	}
	if(cur) return vis[tmp[++tl]=to]=true;
	else return false;
}
bool dfs(int dep,int to){
	if(dep>k){
		cur=k+1;tl=0;
		bool flag=check(to);
		FOR(i,1,tl) vis[tmp[i]]=false;
		return flag;
	}
	FOR(i,1,k) if(!used[i]){
		used[i]=true;
		seq[dep]=i;
		bool flag=dfs(dep+1,to);
		used[i]=false;
		if(flag) return true;
	}
	return false;
}
int main(){
	k=read();n=read();
	FOR(i,1,k) sx[i]=read(),sy[i]=read();
	FOR(i,1,n) tx[i]=read(),ty[i]=read();
	FOR(i,1,k){
		piv=i;
		FOR(j,1,n) p[j]=j;
		sort(p+1,p+n+1);
		FOR(j,1,n) FOR(k,1,n) if(j!=p[k] && on(tx[p[k]],ty[p[k]],sx[i],sy[i],tx[j],ty[j])) v[i][j].push_back(p[k]);
	}
	FOR(i,1,n) if(dfs(1,i)) ans++;
	printf("%d\n",ans);
}

CF700E Cool Slogans

CF704B Ant Man

CF704C Black Widow

CF704D Captain America

CF704E Iron Man

CF708D Incorrect Flow

CF708E Student's Camp

校内模拟赛搬了这题,感觉全场就我没见过……虽然还是硬做出来了(

首先,每一排留下的肯定是一个区间。如果什么都没留下了,那就不合法了(

同时,相邻两排的区间一定有交。

那么先来个五次方暴力:\(f_{i,l,r}\) 表示算了前 \(i\) 排,第 \(i\) 排的区间是 \([l,r]\),且合法的概率。

转移暴力枚举上一个区间是什么。这一排变成这个区间的概率是个组合式子。

然后很容易优化到三次方:反面考虑,上一排所有区间减去没有交的区间。没有交的区间就两种:它的 \(l\) 大于这一排的 \(r\),或者它的 \(r\) 小于这一排的 \(l\)。前缀和/后缀和。

怎么变成平方呢?状态数就三次方了,所以我们不管原来的状态,直接算出前缀和和后缀和。

这里以前缀和为例。我们先算出所有右端点恰好为 \(j\) 的概率之和。大概是个形如 \(\displaystyle c\sum_{i=1}^j(s-x_i)\binom{k}{i-1}p^{i-1}(1-p)^{k-(i-1)}\) 的式子。

后面这个可能可以接着化,但此时再上个前缀和就够了。

由于场上赶时间,写得比较丑。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int> PII;
const int maxn=1515,maxk=222222,mod=1000000007;
#define MP make_pair
#define PB push_back
#define lson o<<1,l,mid
#define rson o<<1|1,mid+1,r
#define FOR(i,a,b) for(int i=(a);i<=(b);i++)
#define ROF(i,a,b) for(int i=(a);i>=(b);i--)
#define MEM(x,v) memset(x,v,sizeof(x))
inline ll read(){
	char ch=getchar();ll x=0,f=0;
	while(ch<'0' || ch>'9') f|=ch=='-',ch=getchar();
	while(ch>='0' && ch<='9') x=x*10+ch-'0',ch=getchar();
	return f?-x:x;
}
int n,m,p,k,pre[2][maxn],suf[2][maxn],cur,fac[maxk],inv[maxk],invfac[maxk],ans,pw1[maxk],pw2[maxk];
inline int qpow(int a,int b){
	int ans=1;
	for(;b;b>>=1,a=1ll*a*a%mod) if(b&1) ans=1ll*ans*a%mod;
	return ans;
}
inline int C(int n,int m){
	if(n<m || n<0 || m<0) return 0;
	return 1ll*fac[n]*invfac[m]%mod*invfac[n-m]%mod;
}
int main(){
	n=read();m=read();
	p=read();p=1ll*p*qpow(read(),mod-2)%mod;
	k=read();
	fac[0]=fac[1]=inv[1]=invfac[0]=invfac[1]=1;
	FOR(i,2,k){
		fac[i]=1ll*fac[i-1]*i%mod;
		inv[i]=mod-1ll*(mod/i)*inv[mod%i]%mod;
		invfac[i]=1ll*invfac[i-1]*inv[i]%mod;
	}
	pw1[0]=pw2[0]=1;
	FOR(i,1,2*k){
		pw1[i]=1ll*pw1[i-1]*p%mod;
		pw2[i]=1ll*pw2[i-1]*(1-p+mod)%mod;
	}
	cur=1;
	FOR(i,1,m) FOR(j,i,m) if(i-1<=k && m-j<=k){
		int x=1ll*C(k,i-1)*C(k,m-j)%mod*pw1[i-1+m-j]%mod*pw2[2*k-(i-1+m-j)]%mod;
		pre[0][j]=(pre[0][j]+x)%mod;
		suf[0][i]=(suf[0][i]+x)%mod;
	}
	FOR(i,1,m) pre[0][i]=(pre[0][i-1]+pre[0][i])%mod;
	ROF(i,m,1) suf[0][i]=(suf[0][i+1]+suf[0][i])%mod;
	FOR(_,2,n){
		FOR(i,1,m) pre[cur][i]=suf[cur][i]=0;
		int s1=0,s2=0;
		FOR(j,1,m){
			if(j-1<=k){
				s1=(s1+1ll*C(k,j-1)*pw1[j-1]%mod*pw2[k-(j-1)])%mod;
				s2=(s2+1ll*C(k,j-1)*pw1[j-1]%mod*pw2[k-(j-1)]%mod*pre[cur^1][j-1])%mod;
			}
			if(m-j<=k){
				int c=1ll*C(k,m-j)%mod*pw1[m-j]%mod*pw2[k-(m-j)]%mod;
				int s=(pre[cur^1][m]-suf[cur^1][j+1]+mod)%mod;
				pre[cur][j]=(pre[cur][j]+1ll*c*(1ll*s*s1%mod-s2+mod))%mod;
			}
		}
		s1=s2=0;
		ROF(i,m,1){
			if(m-i<=k){
				s1=(s1+1ll*C(k,m-i)*pw1[m-i]%mod*pw2[k-(m-i)])%mod;
				s2=(s2+1ll*C(k,m-i)*pw1[m-i]%mod*pw2[k-(m-i)]%mod*suf[cur^1][i+1])%mod;
			}
			if(i-1<=k){
				int c=1ll*C(k,i-1)%mod*pw1[i-1]%mod*pw2[k-(i-1)]%mod;
				int s=(pre[cur^1][m]-pre[cur^1][i-1]+mod)%mod;
				suf[cur][i]=(suf[cur][i]+1ll*c*(1ll*s*s1%mod-s2+mod))%mod;
			}
		}
		FOR(i,1,m) pre[cur][i]=(pre[cur][i-1]+pre[cur][i])%mod;
		ROF(i,m,1) suf[cur][i]=(suf[cur][i+1]+suf[cur][i])%mod;
		cur^=1;
	}
	printf("%d\n",pre[cur^1][m]);
}

[AGC020D] Min Max Repetition

[AGC020E] Encoding Subsets

有坑待填!有坑待填!有坑待填!!!

考虑暴力。(¿¿¿)

\(f_S\) 表示字符串 \(S\) 的所有子集的重写方案之和,\(g_S\) 表示字符串 \(S\) 的所有子集的不可分割的(指不能拆出一些带 \(\texttt{x}\) 的,\(\texttt{0011101}\) 这种视为不可分割)重写方案之和。

边界 \(g_0=1,g_1=2\),答案为 \(f_{\text{原串}}\)

\[f_S=g_S+\sum_{i=1}^{n-1}f_{S[1\dots i]}g_{S[i+1\dots n]} \]

\[g_S=\sum_{i|n,i\ne n}f_{S[1\dots i]\& S[i+1\dots 2i]\& S[2i+1\dots3i]\&\dots} \]

上面是枚举最后一个可分割的位置,下面是枚举循环节长度。

上记忆化搜索。然后瞎写都行。

时间复杂度证明:

一个串能出现在状态里,是因为它是原串,或者某个可到达的状态的子串,或者某个可到达的状态缩起来的。

  • 对于长度 \(\le\frac{n}{8}\) 的串,假如出现在状态中,也至多是 \(O(2^{n/8})\) 种。
  • 对于长度 \(>\frac{n}{8}\) 的串,至多被缩两次:
    • 对于没被缩的串,也就是原串的子串,只有 \(O(n^2)\) 种。
    • 对于只被缩一次的串,可以被表示成 \(S[i\dots i+k-1]\& S[j\dots j+k-1]\),只有 \(O(n^3)\) 种。
    • 对于被缩两次的串:(其实,下面这一块我不懂为什么不是 \(O(n^4)\)……)
      • 对于选个子串,\(2\) 倍缩,再选个子串,再 \(2\) 倍缩,最后选个子串的,一定可以表示成 \(S[i\dots i+k-1]\& S[i+k\dots i+2k-1]\& S[j\dots j+k-1]\& S[j+k\dots j+2k-1]\) 的形式。所以一共是 \(O(n^3)\) 种。
      • 对于 \(2\) 倍缩 \(3\) 倍缩的,类似;
      • 对于 \(3\) 倍缩 \(2\) 倍缩的,类似。

所以状态数上界 \(O(n^3+2^{n/8})\),其中 \(n^3\) 前面还带着个小常数。(如果是 \(O(n^4)\) 也带着个超小常数就对了……)

实践证明,状态数至多只有大概 \(5\times 10^4\)。上面给出的上界仍然是比较松的。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int> PII;
const int mod=998244353;
#define MP make_pair
#define PB push_back
#define lson o<<1,l,mid
#define rson o<<1|1,mid+1,r
#define FOR(i,a,b) for(int i=(a);i<=(b);i++)
#define ROF(i,a,b) for(int i=(a);i>=(b);i--)
#define MEM(x,v) memset(x,v,sizeof(x))
inline ll read(){
	char ch=getchar();ll x=0,f=0;
	while(ch<'0' || ch>'9') f|=ch=='-',ch=getchar();
	while(ch>='0' && ch<='9') x=x*10+ch-'0',ch=getchar();
	return f?-x:x;
}
string s;
map<string,int> f,g;
int G(string);
int F(string s){
	if(f.count(s)) return f[s];
	int n=s.size(),ans=G(s);
	FOR(i,1,n-1) ans=(ans+1ll*F(s.substr(0,i))*G(s.substr(i,n)))%mod;
	return f[s]=ans;
}
int G(string s){
	if(g.count(s)) return g[s];
	int n=s.size();
	if(n==1) return s[0]-'0'+1;
	int ans=0;
	FOR(i,1,n-1) if(n%i==0){
		string t;
		FOR(j,0,i-1){
			bool flag=true;
			for(int k=j;k<n;k+=i) flag&=s[k]-'0';
			t+=flag+'0';
		}
		ans=(ans+F(t))%mod;
	}
	return g[s]=ans;
}
int main(){
	cin>>s;
	cout<<F(s)<<endl;
}

[AGC020F] Arcs on a Circle

[AGC021E] Ball Eat Chameleons

[AGC021F] Trinity

[AGC022D] Shopping

[AGC022E] Median Replace

考虑怎么判一个串合法。

从左往右扫,维护一个栈,栈从底到顶是 111...11000..00 的形式,为什么后面会说。

遇到一个 0 的时候:

  • 如果栈顶两个 0,那么最优方案是把这连续的三个 0 操作一次变成一个 0,所以弹掉一个 0。
  • 否则压一个 0,等待把它们弹掉。

遇到一个 1 的时候:

  • 如果栈顶是 0,那么最优方案是操作一次(001, 101 都是操作一次更优,因为中间那个 0 无论如何我们都想把它删掉,如果留着 1 去干后面的事浪费的只能是 1),所以弹掉一个 0。
  • 否则压一个 1。

最后只要栈内 1 的个数不少于 0 的个数即可。(在 01 交界处,每次消耗掉两个 1 和一个 0 变成一个 1。注意到最后栈中剩下奇数个数,所以 0 的个数和 1 的个数不相等)

注意到栈中 0 的个数永远不超过 2,且 1 的个数不减。所以如果 1 的个数大于 2,可以看成就是 2。

这样栈就只剩九种状态,可以 DP 了。

由于毕姥爷给我们讲这题时上了个自动机(当时完全掉线),所以这里也写了个自动机的样子(看起来高档一点)

时间复杂度 \(O(n)\)

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int> PII;
const int maxn=300030,mod=1000000007,to[9][2]={{1,3},{2,0},{1,1},{4,6},{5,3},{4,4},{7,6},{8,6},{7,7}};
#define MP make_pair
#define PB push_back
#define lson o<<1,l,mid
#define rson o<<1|1,mid+1,r
#define FOR(i,a,b) for(int i=(a);i<=(b);i++)
#define ROF(i,a,b) for(int i=(a);i>=(b);i--)
#define MEM(x,v) memset(x,v,sizeof(x))
inline ll read(){
	char ch=getchar();ll x=0,f=0;
	while(ch<'0' || ch>'9') f|=ch=='-',ch=getchar();
	while(ch>='0' && ch<='9') x=x*10+ch-'0',ch=getchar();
	return f?-x:x;
}
int n,f[maxn][9],ans;
char s[maxn];
int main(){
	scanf("%s",s+1);
	n=strlen(s+1);
	f[0][0]=1;
	FOR(i,0,n-1) FOR(j,0,8){
		if(s[i+1]!='1') f[i+1][to[j][0]]=(f[i+1][to[j][0]]+f[i][j])%mod;
		if(s[i+1]!='0') f[i+1][to[j][1]]=(f[i+1][to[j][1]]+f[i][j])%mod;
	}
	FOR(i,3,8) if(i!=5) ans=(ans+f[n][i])%mod;
	printf("%d\n",ans);
}

[AGC022F] Checkers

[AGC023D] Go Home

[AGC023E] Inversions

[AGC023F] 01 on Tree

[AGC024D] Isomorphism Freak

[AGC024E] Sequence Growing Hard

[AGC024F] Simple Subsequence Problem

[AGC025D] Choosing Points

[AGC025E] Walking on a Tree

[AGC025F] Addition and Andition

[AGC026D] Histogram Coloring

少数我自己能想出来的集训队作业(

而且还比大众做法快(

本题有很多做法,这里是一个比较难想(?),比较难写,但是快的做法。

首先注意到如果相邻两个方块同色,那么包含它的一个 \(4\times 4\) 的正方形的剩下两个方块只有一种涂法,而且这两个方块的颜色一样,都是前两个方块的颜色的反。

如果相邻两个方块不同色,那么包含它的一个 \(4\times 4\) 的正方形的剩下两个方块有两种涂法,只需要两个方块颜色不同。

\(f_{l,r,a,b,x}\) 表示考虑 \([l,r]\) 这段区间,如果把 \(\min\limits_{l\le i\le r} h_i\) 这个高度看成 \(1\) (也就是砍掉最小值以下的),左下角颜色是 \(a\),右下角颜色是 \(b\),最下面一排有没有两个相邻的颜色一样,的方案数。边界 \(l=r\) 自己各种考虑,答案是所有 \(f_{1,n,a,b,x}\) 的和。

转移,拎出 \([l,r]\) 里第一个取到最小值的位置,会将 \([l,r]\) 断成至多两段。最后 \(f_{l,r}\) 就是由这些段往下拉几行和一个高为 \(1\) 的列合并起来。

下拉一个区间,合并两个区间,都能大力枚举。

并不是所有区间都有用。建个笛卡尔树,就只有结点那些区间有用,共 \(O(n)\) 个。建笛卡尔树也可以 \(O(n)\)

总时间复杂度 \(O(n)\),带个 \(30+\) 的常数。

最近没事干把代码精细实现了,应该是 \(O(n)\) 的。忽略快速幂吧不想改了,反正不是瓶颈

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=111111,mod=1000000007,inv2=500000004;
#define FOR(i,a,b) for(int i=(a);i<=(b);i++)
#define ROF(i,a,b) for(int i=(a);i>=(b);i--)
#define MEM(x,v) memset(x,v,sizeof(x))
inline int read(){
    int x=0,f=0;char ch=getchar();
    while(ch<'0' || ch>'9') f|=ch=='-',ch=getchar();
    while(ch>='0' && ch<='9') x=x*10+ch-'0',ch=getchar();
    return f?-x:x;
}
int n,h[maxn],ls[maxn],rs[maxn],stk[maxn],tp,rt,dp[maxn][2][2][2],tmp[2][2][2],ans;
inline int qpow(int a,int b){
    int ans=1;
    for(;b;b>>=1,a=1ll*a*a%mod) if(b&1) ans=1ll*ans*a%mod;
    return ans;
}
void go_lower(int f[][2][2],int cnt){
    if(!cnt) return;
    int g[2][2][2];
    MEM(g,0);
    FOR(a,0,1) FOR(b,0,1) FOR(c,0,1) if(c){
        if(cnt%2==0) g[a][b][c]=f[a][b][c];
        else g[a^1][b^1][c]=f[a][b][c];
    }
    else{
        int x=qpow(2,cnt-1);
        g[a][b][c]=(g[a][b][c]+1ll*f[a][b][c]*x)%mod;
        g[a^1][b^1][c]=(g[a^1][b^1][c]+1ll*f[a][b][c]*x)%mod;
    }
    FOR(a,0,1) FOR(b,0,1) FOR(c,0,1) f[a][b][c]=g[a][b][c];
}
void merge(int f[][2][2],int g[][2][2]){
    int h[2][2][2];
    MEM(h,0);
    FOR(a,0,1) FOR(b,0,1) FOR(c,0,1) FOR(d,0,1) FOR(e,0,1) FOR(f_,0,1){
        int to=c|f_|(b==d);
        h[a][e][to]=(h[a][e][to]+1ll*f[a][b][c]*g[d][e][f_])%mod;
    }
    FOR(a,0,1) FOR(b,0,1) FOR(c,0,1) f[a][b][c]=h[a][b][c];
}
void dfs(int x){
    if(ls[x]){
        dfs(ls[x]);
        go_lower(dp[ls[x]],h[ls[x]]-h[x]);
        FOR(a,0,1) FOR(b,0,1) FOR(c,0,1) dp[x][a][b][c]=dp[ls[x]][a][b][c];
        merge(dp[x],tmp);
    }
    else dp[x][0][0][0]=dp[x][1][1][0]=1;
    if(rs[x]){
        dfs(rs[x]);
        go_lower(dp[rs[x]],h[rs[x]]-h[x]);
        merge(dp[x],dp[rs[x]]);
    }
}
int main(){
    n=read();
    FOR(i,1,n) h[i]=read();
    FOR(i,1,n){
        while(tp && h[i]<h[stk[tp]]) ls[i]=stk[tp--];
        if(!tp) rt=i;
        else rs[stk[tp]]=i;
        stk[++tp]=i;
    }
    tmp[0][0][0]=tmp[1][1][0]=1;
    dfs(rt);
    go_lower(dp[rt],h[rt]-1);
    FOR(a,0,1) FOR(b,0,1) FOR(c,0,1) ans=(ans+dp[rt][a][b][c])%mod;
    printf("%d\n",ans);
    return 0;
}

[AGC026E] Synchronized Subsequence

[AGC026F] Manju Game

[AGC027D] Modulo Matrix

[AGC027E] ABBreviate

[AGC027F] Grafting

[AGC028C] Min Cost Cycle

[AGC028D] Chords

[AGC028E] High Elements

[AGC028F] Reachable Cells

[AGC029C] Lexicographic constraints

一上来感觉挺可做,想了个不知道什么鬼,写了一半被告知这题是模拟。

然后又发现自己想的假了。然后发现这个模拟似乎很显然(

怎样才能避免把模拟想复杂啊(

先看看字符集大小是 \(1\) 可不可行(不然下面还要判很多东西)。当且仅当序列严格递增。

二分一下,用栈维护所有字符不为最小的字符的位置(和这个位置上的字符)。

然后从左往右扫:

  • \(a_i>a_{i-1}\),加一堆最小的字符即可。所以栈不变。
  • 否则,先把栈顶 \(>a_i\) 的位置弹出。然后 \(a_i\) 这个位置的字符需要变大一个。
    • 若没有超出字符集大小,那就好。
    • 若超出字符集大小,则这个位置设为最小的字符,前一个位置需要变大一个。如果还不行就继续。
    • 若到最后要修改第 0 个位置,那就是不行,否则可以。

时间复杂度 \(O(n\log n)\)

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int> PII;
const int maxn=200020,mod=998244353;
#define MP make_pair
#define PB push_back
#define lson o<<1,l,mid
#define rson o<<1|1,mid+1,r
#define FOR(i,a,b) for(int i=(a);i<=(b);i++)
#define ROF(i,a,b) for(int i=(a);i>=(b);i--)
#define MEM(x,v) memset(x,v,sizeof(x))
inline ll read(){
	char ch=getchar();ll x=0,f=0;
	while(ch<'0' || ch>'9') f|=ch=='-',ch=getchar();
	while(ch>='0' && ch<='9') x=x*10+ch-'0',ch=getchar();
	return f?-x:x;
}
int n,a[maxn];
vector<PII> v;
bool add(int x,int upr){
	if(!x) return false;
	if(!v.empty()) assert(x>=v.back().first);
	if(!v.empty() && x==v.back().first){
		v.back().second++;
		if(v.back().second>upr){
			v.pop_back();
			return add(x-1,upr);
		}
	}
	else v.PB(MP(x,2));
	return true;
}
bool check(int upr){
	v.clear();
	FOR(i,1,n) if(a[i]<=a[i-1]){
		while(!v.empty() && v.back().first>a[i]) v.pop_back();
		if(!add(a[i],upr)) return false;
	}
	return true;
}
int main(){
	n=read();
	FOR(i,1,n) a[i]=read();
	bool flag=true;
	FOR(i,1,n-1) flag&=a[i]<a[i+1];
	if(flag) return puts("1"),0; 
	int l=2,r=n;
	while(l<r){
		int mid=(l+r)>>1;
		if(check(mid)) r=mid;
		else l=mid+1;
	}
	printf("%d\n",l);
}

[AGC029E] Wandering TKHS

posted @ 2020-04-04 14:53  ATS_nantf  阅读(353)  评论(0编辑  收藏  举报