把博客园图标替换成自己的图标
把博客园图标替换成自己的图标end

Public NOIP Round #4 (Div. 1, 提高)

写了两个和std不一样的做法(雾,然后还拿了一个最优解。

治病

容易发现是线段覆盖问题,因此只要对每个线段离散以后数出只有它一个线段覆盖的段即可。

时间复杂度\(O(\sum k\log \sum k+n+m)\)

code:

#include<bits/stdc++.h>
#define Gc() getchar() 
#define Me(x,y) memset(x,y,sizeof(x))
#define Mc(x,y) memcpy(x,y,sizeof(x))
#define d(x,y) ((m)*(x-1)+(y))
#define R(n) (rnd()%(n)+1)
#define Pc(x) putchar(x)
#define LB lower_bound
#define UB upper_bound
#define PB push_back
using ll=long long;using db=double;using lb=long db;using ui=unsigned;using ull=unsigned ll;
using namespace std;const int N=1e6+5,M=N*4+5,K=1e5+5,mod=1e9+7,Mod=mod-1,INF=1e9+7;const db eps=1e-5;mt19937 rnd(time(0));
int n,m,k,x,y,z,l,r,A[N],Bh,B[N],Sum[N],Ct[N];ll Ans[N],ToT;
struct Node{int l,r,id;};vector<Node> S[N];
int main(){
	freopen("doctor.in","r",stdin);freopen("doctor.out","w",stdout);
	int i,j;scanf("%d%d",&n,&m);for(i=1;i<=m;i++) scanf("%d",&A[i]);for(i=1;i<=n;i++){
		scanf("%d%d%d",&l,&r,&x);while(x--) scanf("%d",&y),S[y].PB((Node){l,r,i});
	}for(i=1;i<=m;i++){
		Bh=0;for(Node j:S[i]) B[++Bh]=j.l,B[++Bh]=j.r+1;sort(B+1,B+Bh+1);Bh=unique(B+1,B+Bh+1)-B-1;
		for(j=0;j<S[i].size();j++) Sum[S[i][j].l=LB(B+1,B+Bh+1,S[i][j].l)-B]++,Sum[S[i][j].r=LB(B+1,B+Bh+1,S[i][j].r+1)-B]--;
		for(j=1;j<=Bh;j++) Sum[j]+=Sum[j-1],Sum[j]&&(ToT+=1ll*(B[j+1]-B[j])*A[i]),Ct[j]=Ct[j-1]+(Sum[j]==1?B[j+1]-B[j]:0);
		for(Node j:S[i]) Ans[j.id]+=1ll*(Ct[j.r-1]-Ct[j.l-1])*A[i];for(j=1;j<=Bh;j++) Sum[j]=0;
	}for(i=1;i<=n;i++) printf("%lld%c",ToT-Ans[i]," \n"[i==n]);
}

拓扑序计数

首先显然先\(O(n2^n)\)预处理出\(f_{S}\)表示拓扑序前面\(|S|\)个点是\(S\)的方案数,\(g_S\)表示拓扑序后面\(|S|\)个点是\(S\)的方案数。

枚举在后面的每个点\(i\),然后枚举拓扑序在它前面的集合\(S\),然后将所有\(x\in S\)的点\((x,i)\)加上\(f_{S}\times g_{S'}\),其中\(S'\)\(S\)的补集。

听说这样\(O(n^22^n)\)就能过,咱也不知道,咱也不敢说。

现在问题抽象为求包含一个点的集合的和,我们从\(1\)\(2^n-1\)枚举这个集合,因为每次加一均摊变化\(O(1)\)次二进制位,所以对这些差分后暴力修改即可。就可以将时间复杂度降到\(O(n2^n)\)

std比这个似乎牛逼,大概按照前缀和的方法每次将后一半合并到前一半。

code:

#include<bits/stdc++.h>
#define Gc() getchar() 
#define Me(x,y) memset(x,y,sizeof(x))
#define Mc(x,y) memcpy(x,y,sizeof(x))
#define d(x,y) ((m)*(x-1)+(y))
#define R(n) (rnd()%(n)+1)
#define Pc(x) putchar(x)
#define LB lower_bound
#define UB upper_bound
#define PB push_back
using ll=long long;using db=double;using lb=long db;using ui=unsigned;using ull=unsigned ll;
using namespace std;const int N=20+5,M=(1<<20)+5,K=1e5+5,mod=1e9+7,Mod=mod-1,INF=1e9+7;const db eps=1e-5;mt19937 rnd(time(0));
int n,m,k,x,y,z,I1[N],I2[N];ll f[M],g[M],Ans[N],ToT;
void Solve(){
	int i,j;scanf("%d%d",&n,&m);k=(1<<n);Me(I1,0);Me(I2,0);while(m--) scanf("%d%d",&x,&y),x--,y--,I1[x]|=(1<<y),I2[y]|=(1<<x);
	f[0]=g[0]=1;for(i=1;i<k;i++){f[i]=g[i]=0;
		for(j=0;j<n;j++) if(i>>j&1&&!(I1[j]&(i^(1<<j)))&&(I2[j]&(i^(1<<j)))==I2[j]) f[i]+=f[i^(1<<j)];
		for(j=0;j<n;j++) if(i>>j&1&&!(I2[j]&(i^(1<<j)))&&(I1[j]&(i^(1<<j)))==I1[j]) g[i]+=g[i^(1<<j)]; 
	}for(i=0;i<n;i++){
		Me(Ans,0);for(j=1;j<k;j++){
			x=0;while((j-1)>>x&1) Ans[x++]+=ToT;Ans[x]-=ToT;
			if(j>>i&1||j&I1[i]||(j&I2[i])^I2[i]) continue;ToT+=f[j]*g[(k-1)^j^(1<<i)]; 
		}for(j=0;j<n;j++) printf("%lld%c",Ans[j]+ToT," \n"[j==n-1]);
	}
} 
int main(){
	freopen("topo.in","r",stdin);freopen("topo.out","w",stdout);
	int T;scanf("%d",&T);while(T--) Solve();
}

序列

看这个诡异的修改似乎不能维护任何东西,修改次数很少,考虑每次修改以后暴力。

首先因为答案可以二分,因此可以得到一个\(O(mn\log n)\)的做法。

根据这个二分我们可以得出的一些基本结论:

1.如果一个长度\(Len\)可以,那么\(Len-1\)也可以。

2.对于一个成立的区间,保留最大值任意删除左右两边都是成立的。

因此我们考虑用双指针来维护这个东西。这里注意双指针指的是答案单调。

从左往右扫,设之前的答案为\(Ans\),每次查询\([i-Ans,i]\)这个区间合不合法,如果合法就向左扩展,否则将\(i-Ans\)这个点删掉。这个东西显然是正确的,因为它一定会在答案区间的最大值处扩展出这个右端点,从而取到答案。

但是这个的复杂度呢?考虑每次加入/删除要么是下标加一,要么答案加一,因此总修改次数是\(O(n)\)的。

但是如果你用一个堆或其它什么东西维护那就还是\(O(mn\log n)\),那么恭喜你白做了。

这个东西只在头上删除,但是两边都能加入,考虑对顶栈,维护两个栈前缀max和min,每次直接在第一个栈删除,然后如果第一个栈没了那么就把第二个栈全搬到第一个栈里面,显然均摊\(O(n)\)。所以我们在\(O(nm)\)的时间复杂度内得到了答案。

目前rk1,比笛卡尔树上dp快\(1.5\)\(2\)倍。

code:

#include<bits/stdc++.h>
#define Gc() getchar() 
#define Me(x,y) memset(x,y,sizeof(x))
#define Mc(x,y) memcpy(x,y,sizeof(x))
#define d(x,y) ((m)*(x-1)+(y))
#define R(n) (rnd()%(n)+1)
#define Pc(x) putchar(x)
#define LB lower_bound
#define UB upper_bound
#define PB push_back
using ll=long long;using db=double;using lb=long db;using ui=unsigned;using ull=unsigned ll;
using namespace std;const int N=1e6+5,M=N*4+5,K=1e5+5,mod=1e9+7,Mod=mod-1,INF=1e9+7;const db eps=1e-5;mt19937 rnd(time(0));
int n,m,k,x,y,fa[N],A[N],B[N],Bh,Mx[N],Le[N],vis[N],Ans;
int GF(int x){return fa[x]^x?fa[x]=GF(fa[x]):x;}
namespace ST{
	int S1[N],S2[N],H1,H2,X1[N],X2[N],I1[N],I2[N];void Cl(){H1=H2=0;X1[0]=X2[0]=-INF;I1[0]=I2[0]=INF*2;}
	void push_front(int x){S1[++H1]=x;X1[H1]=max(A[x],X1[H1-1]);I1[H1]=min(A[x],I1[H1-1]);}
	void push_back(int x){S2[++H2]=x;X2[H2]=max(A[x],X2[H2-1]);I2[H2]=min(A[x],I2[H2-1]);}
	void pop(){if(!H1) {H1=H2;for(int i=1;i<=H1;i++) S1[i]=S2[H1-i+1],X1[i]=max(X1[i-1],A[S1[i]]),I1[i]=min(I1[i-1],A[S1[i]]);H2=0;}H1--;}
	int Qx(){return max(X1[H1],X2[H2]);}int Qi(){return min(I1[H1],I2[H2]);}
}
void calc(){
	int i,R=1;Ans=0;ST::Cl();for(i=1;i<=n;i++){
		ST::push_back(i);while(ST::Qx()+ST::Qi()>i-R+1&&R>1) ST::push_front(--R);if(ST::Qx()+ST::Qi()<=i-R+1) ST::pop(),R++;
		Ans=max(Ans,i-R+1);
	}printf("%d\n",Ans);
}
int main(){
	freopen("loose.in","r",stdin);freopen("loose.out","w",stdout); 
	int i;scanf("%d%d",&n,&m);for(i=1;i<=n;i++) scanf("%d",&A[i]);calc();while(m--){scanf("%d",&k);while(k--) scanf("%d%d",&x,&y),swap(A[x],A[y]);calc();}
}

水果

没看懂题解,先鸽。

posted @ 2022-11-22 08:19  275307894a  阅读(124)  评论(0编辑  收藏  举报
浏览器标题切换
浏览器标题切换end