【学习笔记】NOIP暴零赛3

博弈(game)

观察到博弈过程中胜负态不会发生改变,那么求出从每个棋子出发能走的最长链,然后背包即可。

复杂度 O ( n m ) O(nm) O(nm)

#include<bits/stdc++.h>
#define ll long long
#define pb push_back
using namespace std;
const int mod=998244353;
const int N=305;
int n,m,dp[N],in[N],g[N*N],g2[N*N],vis[N];
ll res;
string s;
queue<int>Q; 
vector<int>ve[N];
int main(){
	ios::sync_with_stdio(false);
	cin.tie(0),cout.tie(0);
	cin>>n>>m>>s;
	for(int i=1;i<=m;i++){
		int u,v;cin>>u>>v,u--,v--;
		if(u>v)swap(u,v);ve[v].pb(u),in[u]++;
	}for(int i=0;i<n;i++)if(!in[i])Q.push(i);
	while(Q.size()){
		int u=Q.front();Q.pop();
		for(auto v:ve[u]){
			if(--in[v]==0)Q.push(v);
			if(s[u]==s[v])dp[v]=max(dp[v],dp[u]+1),vis[v]=1;
			else if(s[u]!=s[v]&&!vis[u]&&!dp[u])dp[v]=max(dp[v],1);
		}
	}g[0]=g2[0]=1;
	for(int i=0;i<n;i++){
		if(s[i]=='W'){
			for(int j=n*n;j>=dp[i];j--){
				g[j]=(g[j]+g[j-dp[i]])%mod;
			}
		}
		else{
			for(int j=n*n;j>=dp[i];j--){
				g2[j]=(g2[j]+g2[j-dp[i]])%mod;
			}
		}
	}
	for(int i=1;i<=n*n;i++){
		g2[i]=(g2[i-1]+g2[i])%mod;
	}for(int i=1;i<=n*n;i++){
		res=(res+(ll)g[i]*g2[i-1])%mod;
	}cout<<res;
} 

排列 (perm)

神仙题。

考虑怎么转化这个性质。等价于,不能存在一个区间,里面同时存在 ( a , b ) , ( b , c ) (a,b),(b,c) (a,b),(b,c)而不存在 ( a , c ) (a,c) (a,c)

由此可以想到传递闭包:每个区间,如果建一个图,就都必须具有传递性。

然后我们发现,只需要每个前缀都有传递性,每个后缀都有传递性就好了。

不要问我怎么想到的,以及证明,国内谜语人太多了,可以去骂写题解的人

也就是说,任取一个 i i i,设 G i G_i Gi为考虑前 i i i条边组成的图,那么 G i G_i Gi G i G_i Gi的补图都有传递性。

有一个很神奇的结论:满足这样条件的 G G G恰好有 n ! n! n!个,这是因为,任取一个排列 p p p,如果 a < b a<b a<b并且 a a a p p p中的位置在 b b b的后面,那么连边 ( a , b ) (a,b) (a,b),这样构造出来的图一定是满足传递性的,并且这是一个双射。

然后,最无脑的想法是,因为合法的前缀只有 n ! n! n!个,所以状态数只有 O ( n ! ) O(n!) O(n!),也就是说可以直接把整张图的每条边存起来,然后暴力转移。不过既然我们都知道这样的 G G G和一个排列 p p p构成双射了,那么在 p p p中转移则显得合情合理,一个合法的 G G G加入一条边 ( a , b ) (a,b) (a,b)后仍然合法,当且仅当 G G G对应的排列 p p p a a a恰好在 b b b前面的位置,加入边 ( a , b ) (a,b) (a,b)相当于是交换 ( a , b ) (a,b) (a,b)的位置。

复杂度 O ( n ! × n ) O(n!\times n) O(n!×n)

#include<bits/stdc++.h>
using namespace std;
const int mod=998244353;
int n,m,fac[11],p[11],vs[11][11];
int A[35],B[35],C[35],D[35],dp[3628805];
int dfs(int x){
	int id=0;
	for(int i=1;i<=n;i++){
		int x=p[i];
		for(int j=1;j<i;j++)if(p[j]<p[i])x--;
		id+=fac[n-i]*(x-1);
	}if(~dp[id])return dp[id];
	if(x>n*(n-1)/2)return 1;
	dp[id]=0;
	for(int i=1;i<=m;i++){
		if(vs[C[i]][D[i]]&&!vs[A[i]][B[i]])return 0;
	}
	for(int i=1;i<n;i++){
		if(p[i]<p[i+1]){
			vs[p[i]][p[i+1]]=1,swap(p[i],p[i+1]),dp[id]=(dp[id]+dfs(x+1))%mod,swap(p[i],p[i+1]),vs[p[i]][p[i+1]]=0;
		}
	}return dp[id];
}
int main(){
	memset(dp,-1,sizeof dp),cin>>n>>m;fac[0]=1;for(int i=1;i<=n;i++)fac[i]=fac[i-1]*i,p[i]=i;
	for(int i=1;i<=m;i++)cin>>A[i]>>B[i]>>C[i]>>D[i];
	cout<<dfs(1);
}

子段和 (seg)

直接用数据结构暴力维护就能做到 O ( n 2 log ⁡ w ) O(n^2\log w) O(n2logw),可以得到 70 p t s 70pts 70pts

接下来一步应该容易想到:对于 max ⁡ \max max相同的区间,我们每次要取出 min ⁡ \min min最小的那一个,对于这个过程,我们可以用一个 set \text{set} set来维护。考虑堆套 set \text{set} set,堆里面每个元素维护区间 max ⁡ \max max相同的区间的集合,用 set \text{set} set维护这些区间的最小值的位置。这里有一个比较巧妙的想法,就是把堆中的元素存在一个数组里,这样每次从堆中取元素时就不用把它复制一遍了。

复杂度 O ( n log ⁡ n ) O(n\log n) O(nlogn)

后来发现题解的思路确实更加聪明。

考虑给定 k k k,怎么求 g ( k ) g(k) g(k)呢,二分答案 w w w,令 a a a为给出序列的前缀和, a 0 = 0 a_0=0 a0=0,每次操作就是对一段后缀 − 1 -1 1,最后要使得 ∀ i < j , a j − a i ≤ w \forall i<j,a_j-a_i\le w i<j,ajaiw

直接从前往后贪心,维护一个 lim \text{lim} lim表示后面的 a a a经过操作后都不能超过 lim \text{lim} lim。如果 a i > lim a_i>\text{lim} ai>lim那么就在这里执行 a i − lim a_i-\text{lim} ailim次操作,得到新的 a a a。然后令 lim : = min ⁡ ( lim , a i + w ) \text{lim}:=\min(\text{lim},a_i+w) lim:=min(lim,ai+w)

正解非常脑洞。考虑直接把所有 w w w lim \text{lim} lim一起维护,设这个函数为 L ( w ) L(w) L(w),另外维护 A ( w ) A(w) A(w)表示目前的代价。剩下的就是用数据结构大力乱搞。

先贴一个 70 p t s 70pts 70pts的代码吧。

#include<bits/stdc++.h>
#define ll long long
#define pb push_back
#define inf 0x3f3f3f3f3f3f3f3f
using namespace std;
const int mod=998244353;
int n,p[200005][20],p2[200005][20],Log[200005];
ll a[200005],s[200005],Min[200005][20],Max[200005][20],K,res,inv2=(mod+1)/2;
vector<pair<ll,ll>>v;
int qmin(int l,int r){
	int k=Log[r-l+1];
	return (Min[l][k]<Min[r-(1<<k)+1][k])?p[l][k]:p[r-(1<<k)+1][k];
}
int qmax(int l,int r){
	int k=Log[r-l+1];
	return (Max[l][k]>Max[r-(1<<k)+1][k])?p2[l][k]:p2[r-(1<<k)+1][k];
}
struct node{
	int l,r;
	ll S;
	bool operator <(const node &a)const{
		return s[qmax(l,r)]-S<s[qmax(a.l,a.r)]-a.S;
	} 
};
priority_queue<node>q;
vector<node>vec;
ll Sum(ll l,ll r){
	l%=mod,r%=mod;
	return (r-l+1)*(l+r)%mod*inv2%mod;
}
void calc(ll &K,ll Max,ll x,ll y){
	if(K/x>=y){
		res+=Sum(Max-y+1,Max)*(x%mod)-y;
		res%=mod,K-=x*y;
	}
	else{
		res+=Sum(Max-K/x+1,Max)*(x%mod)-(K/x),res%=mod;
		Max-=K/x,K%=x,res+=(K%mod)*(Max%mod),res%=mod,K=0;
	}
}
int main(){
	ios::sync_with_stdio(false);
	cin.tie(0),cout.tie(0);
	cin>>n;for(int i=2;i<=n;i++)Log[i]=Log[i/2]+1;
	for(int i=1;i<=n;i++){
		cin>>a[i],s[i]=max(s[i-1]+a[i],0ll),Min[i][0]=Max[i][0]=s[i],p[i][0]=p2[i][0]=i;
	}for(int j=1;j<20;j++){
		for(int i=1;i<=n-(1<<j)+1;i++){
			Min[i][j]=min(Min[i][j-1],Min[i+(1<<j-1)][j-1]),Max[i][j]=max(Max[i][j-1],Max[i+(1<<j-1)][j-1]);
			p[i][j]=(Min[i][j]==Min[i][j-1])?p[i][j-1]:p[i+(1<<j-1)][j-1],p2[i][j]=(Max[i][j]==Max[i][j-1])?p2[i][j-1]:p2[i+(1<<j-1)][j-1]; 
		}
	}q.push({1,n,0});
	cin>>K;
	while(q.size()){
		ll MAX=s[qmax(q.top().l,q.top().r)]-q.top().S,S2(inf);vec.clear();
		if(!MAX)break;
		while(q.size()&&s[qmax(q.top().l,q.top().r)]-q.top().S==MAX){
			int l=q.top().l,r=q.top().r;ll S=q.top().S;q.pop();
			S2=min(S2,s[qmin(l,r)]-S),vec.pb({l,r,S});
		}if(q.size())S2=min(S2,MAX-(s[qmax(q.top().l,q.top().r)]-q.top().S));
		for(int i=0;i<vec.size();i++){
			int l=vec[i].l,r=vec[i].r,p=qmin(l,r);ll S=vec[i].S;
			if(s[p]-S-S2==0){
				if(p>l)q.push({l,p-1,S+S2});
				if(p<r)q.push({p+1,r,S+S2});
			}else q.push({l,r,S+S2});
		}calc(K,MAX,vec.size(),S2);
		if(!K)break;
	}
	if(K){
		for(int i=1;i<=n;i++)a[i]=min(a[i],0ll);
		sort(a+1,a+1+n),reverse(a+1,a+1+n);
		for(int i=2;i<=n;i++){
			calc(K,a[i-1],i-1,a[i-1]-a[i]);
			if(!K)break;
		}if(K){
			calc(K,a[n],n,inf);
		}
	}
	cout<<(res+mod)%mod;
} 

正解没调出来。

最后发现被 set \text{set} set阴了。。。。换成 multiset \text{multiset} multiset就过了,我一下午的光阴啊。。。原来 set \text{set} set是按比较函数去重的啊。。。

#include<bits/stdc++.h>
#define ll long long
#define pb push_back
#define inf 0x3f3f3f3f3f3f3f3f
using namespace std;
const int mod=998244353;
int n,m,p[200005][20],p2[200005][20],Log[200005];
ll a[200005],s[200005],Min[200005][20],Max[200005][20],mx[200005],tag[200005],len[200005],K,res,inv2=(mod+1)/2;
struct node2{
	int l,r;ll min;
	bool operator <(const node2&a)const{
		return min<a.min;
	}
};
multiset<node2>S[200005];
int qmin(int l,int r){
	int k=Log[r-l+1];
	return (Min[l][k]<Min[r-(1<<k)+1][k])?p[l][k]:p[r-(1<<k)+1][k];
}
int qmax(int l,int r){
	int k=Log[r-l+1];
	return (Max[l][k]>Max[r-(1<<k)+1][k])?p2[l][k]:p2[r-(1<<k)+1][k];
}
struct cmp{
	bool operator()(int x,int y){
		return mx[x]<mx[y];
	} 
};
priority_queue<int,vector<int>,cmp>q;
ll Sum(ll l,ll r){
	l%=mod,r%=mod;
	return (r-l+1)*(l+r)%mod*inv2%mod;
}
void calc(ll &K,ll Max,ll x,ll y){
	if(K/x>=y){
		res+=Sum(Max-y+1,Max)*(x%mod)-y;
		res%=mod,K-=x*y;
	}
	else{
		res+=Sum(Max-K/x+1,Max)*(x%mod)-(K/x),res%=mod;
		Max-=K/x,K%=x,res+=(K%mod)*(Max%mod),res%=mod,K=0;
	}
}
int merge(int x,int y){
	if(S[x].size()<S[y].size())swap(x,y);
	assert(S[x].size()==len[x]),assert(S[y].size()==len[y]);
	assert(x!=y);
	len[x]+=len[y];
	for(auto z:S[y])S[x].insert({z.l,z.r,z.min-tag[y]+tag[x]});
	assert(len[x]==S[x].size());
	return x;
}
int main(){
	freopen("seg.in","r",stdin);
	freopen("seg.out","w",stdout);
	ios::sync_with_stdio(false);
	cin.tie(0),cout.tie(0);
	cin>>n;for(int i=2;i<=n;i++)Log[i]=Log[i/2]+1;
	for(int i=1;i<=n;i++){
		cin>>a[i],s[i]=max(s[i-1]+a[i],0ll),Min[i][0]=Max[i][0]=s[i],p[i][0]=p2[i][0]=i;
	}
	for(int j=1;j<20;j++){
		for(int i=1;i<=n-(1<<j)+1;i++){
			Min[i][j]=min(Min[i][j-1],Min[i+(1<<j-1)][j-1]),Max[i][j]=max(Max[i][j-1],Max[i+(1<<j-1)][j-1]);
			p[i][j]=(Min[i][j]==Min[i][j-1])?p[i][j-1]:p[i+(1<<j-1)][j-1],p2[i][j]=(Max[i][j]==Max[i][j-1])?p2[i][j-1]:p2[i+(1<<j-1)][j-1]; 
		}
	}S[m=1].insert({1,n,s[qmin(1,n)]}),len[1]=1,mx[1]=s[qmax(1,n)],q.push({1});
	cin>>K;
	while(q.size()){
		int x=q.top();q.pop();
		ll D=S[x].begin()->min-tag[x];
		if(!mx[x])break;
		if(q.size()&&mx[x]-mx[q.top()]<D){
			int y=q.top();q.pop();
			calc(K,mx[x],len[x],mx[x]-mx[y]),tag[x]+=mx[x]-mx[y],mx[x]=mx[y];
			q.push(merge(x,y));
		}else{
			calc(K,mx[x],len[x],D);
			int l=S[x].begin()->l,r=S[x].begin()->r,p=qmin(l,r);S[x].erase(S[x].begin());
			len[x]--,tag[x]+=D,mx[x]-=D;
			if(l<p){
				m++,len[m]=1,tag[m]=0,mx[m]=s[qmax(l,p-1)]-s[p];
				S[m].insert({l,p-1,s[qmin(l,p-1)]-s[p]});
				q.push(m);
			}if(p<r){
				m++,len[m]=1,tag[m]=0,mx[m]=s[qmax(p+1,r)]-s[p];
				S[m].insert({p+1,r,s[qmin(p+1,r)]-s[p]});
				q.push(m);
			}
			if(S[x].size())q.push(x);
		}if(!K)break;
	}
	if(K){
		for(int i=1;i<=n;i++)a[i]=min(a[i],0ll);
		sort(a+1,a+1+n),reverse(a+1,a+1+n);
		for(int i=2;i<=n;i++){
			calc(K,a[i-1],i-1,a[i-1]-a[i]);
			if(!K)break;
		}if(K){
			calc(K,a[n],n,inf);
		}
	}
	cout<<(res+mod)%mod;
} 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值