考试好题选

如果想上AUOJ可以私我

AUOJ1793

如果 \(n,m\) 小一点的话我们可以通过 \(bfs\) 跑多源最短路来实现求每个点的 \(dis\) 但是这题 \(n,m\) 过大考虑优化

我们发现相当于从每个点开始扩散,那么我们考虑将行相邻的两个关键点单独提出来看看发现我们如果确定了要求某一列上点的 \(dis\) ,它必然是单峰的。

于是便可以考虑优化了

我们可以先将每个有关键点的行上的点的 \(dis\) 通过 \(O(km)\) 正着扫一遍和倒着扫一遍求出来

然后,我们按照上面提出的基于单峰的性质的做法,便可以将某一列的 \(dis\) 化为两段,并又因为 \(dis\) 的值域很小所以直接差分维护即可,这部分的时间复杂度为 \(O(kn)\)

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

code
#include <bits/stdc++.h>
#define rep(a,b,c) for(int a=(b);a<=(c);++a)
#define per(a,b,c) for(int a=(b);a>=(c);--a)
#define ll long long
#define vi vector<int>
#define pb emplace_back
#define mem(a,b) memset(a,b,sizeof(a))
using namespace std;
template <typename T> void read(T &t) {
	t=0; char ch=getchar(); int f=1;
	while (ch<'0'||ch>'9') { if (ch=='-') f=-1; ch=getchar(); }
	do { (t*=10)+=ch-'0'; ch=getchar(); } while ('0'<=ch&&ch<='9'); t*=f;
}
const ll p=2.5e9+1;
template<class T>inline void chkmin(T &x,T y) { x=min(x,y); }
inline int inc(ll x,ll y) { if((x+=y)>=p) x-=p; return x; }
inline int dec(ll x,ll y) { if((x-=y)<0) x+=p; return x; }
inline void Inc(ll &x,ll y) { x=inc(x,y); }
inline void Dec(ll &x,ll y) { x=dec(x,y); }
inline ll mval(ll x) { if(x<0) return x+p; if(x>=p) x-=p; return x; }
inline ll ksm(ll a,ll b) { ll ans=1; for(;b;b>>=1,(a*=a)%=p) if(b&1) (ans*=a)%=p; return ans; }
const int inf=0x3f3f3f3f;
const int N=1e5+5;
const int M=5e4+5;
struct Node {
	int x,y;
}a[1005];
inline bool cmp(Node a,Node b) { return a.x==b.x?a.y<b.y:a.x<b.x; }
int dis[1005][M],n,m,k,L[1005],R[1005],X[1005],sz,tag[M];
ll ans,cf[N],aiv,A,B;
inline void init_dis() {
	rep(i,1,sz) {
		rep(j,1,m) tag[j]=0,dis[i][j]=inf;
		rep(K,L[i],R[i]) tag[a[K].y]=1;
		int lst=-inf;
		rep(j,1,m) if(tag[j]) dis[i][j]=0,lst=j;
		else chkmin(dis[i][j],j-lst);
		lst=inf;
		per(j,m,1) if(tag[j]) dis[i][j]=0,lst=j;
		else chkmin(dis[i][j],lst-j);
	}
	rep(i,2,sz) rep(j,1,m) chkmin(dis[i][j],dis[i-1][j]+X[i]-X[i-1]);
	per(i,sz-1,1) rep(j,1,m) chkmin(dis[i][j],dis[i+1][j]+X[i+1]-X[i]); 
}
inline void upd(int r,int l) {
	if(r<l) return;
	cf[l]++; cf[r+1]--;
}
inline void solve() {
	read(n); read(m); read(k); sz=0; ans=aiv=1;
	rep(i,0,n+m) cf[i]=0;
	rep(i,1,k) read(a[i].x),read(a[i].y);
	sort(a+1,a+k+1,cmp);
	rep(i,1,k) if(a[i].x!=a[i-1].x) {
		L[++sz]=i; R[sz]=i;
		X[sz]=a[i].x;
	}
	else R[sz]=i;
	init_dis();
	rep(i,2,sz) if(X[i]!=(X[i-1]+1)) {
		rep(j,1,m) {
			if((dis[i-1][j]+1)>=(dis[i][j]+X[i]-X[i-1]-1)) upd(dis[i][j]+X[i]-X[i-1]-1,dis[i][j]+1);
			else if((dis[i-1][j]+X[i]-X[i-1]-1)<=(dis[i][j]+1)) upd(dis[i-1][j]+X[i]-X[i-1]-1,dis[i-1][j]+1);
			else {
				int maxx=(dis[i][j]+dis[i-1][j]+X[i]-X[i-1])/2;
				upd(maxx,dis[i][j]+1);
				upd(maxx,dis[i-1][j]+1);
				if((dis[i][j]+dis[i-1][j]+X[i]-X[i-1])%2==0) cf[maxx]--,cf[maxx+1]++;
			}
		}
	}
	rep(j,1,m) {
		upd(dis[1][j]+X[1]-1,dis[1][j]+1);
		upd(dis[sz][j]+n-X[sz],dis[sz][j]+1);
	}
	rep(i,1,n+m) cf[i]+=cf[i-1];
	rep(i,1,sz) rep(j,1,m) cf[dis[i][j]]++;
	A=cf[0]; B=1ll*n*m;
	int ct=0;
	while(A!=B) {
		ans=ans*A%p; aiv=aiv*B%p;
		A+=cf[++ct]; A--;
		B--;
	}
	printf("%lld\n",ans*ksm(aiv,p-2)%p);
}
int main() {
	int T; cin>>T; while(T--)
	solve();
}

AUOJ1789

题外话:这题被zxy搬去给了联考(但是他得到这题的来源是yjr)

先考虑如果给出了 \(n-1\) 条边使 \(1-n\) 的点构成了一条链的情况

这个时候我们考虑用线段树去维护

\(val[rt][i(0/1)][j(0/1)]\) 表示,考虑 \(rt\) 这个点,钦定在不考虑 \(0\) 的边的情况下,\(rt\) 这个点的 \(l\)\(r\) 不在同一连通块中,并且在考虑 \(0\) 的边的情况下, \(l\)\(r\) 的连通块与 \(0\) 的联通情况为 \(i\)\(j\) 的情况下的最小代价(读着有点绕但是理解了就没啥了)

\(pushup\) 就是分类讨论(注意不要忘了在不看 \(0\) 的情况下 \(l\)\(r\) 就联通的时候的 \(val\)

但是如果它不是链的话,我们可以尝试将它转化成链,我们考虑先跑一遍 \(1-n\)\(MST\) 对于加完前 \(i\) 条边后的连通块维护 \(L\)\(R\) 表示这个连通块转化成链后链上的端点是哪两个,合并的时候直接将一个的 \(L\) 与另一个的 \(R\) 连边就好了

我们发现这个图的效果等同于原图于是就完了

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

code
#include <bits/stdc++.h>
#define rep(a,b,c) for(int a=(b);a<=(c);++a)
#define per(a,b,c) for(int a=(b);a>=(c);--a)
#define repe(x) for(int yny=head[x],v;yny&&(v=e[yny].v);yny=e[yny].u)
#define ll long long
#define mem(a,b) memset(a,b,sizeof(a))
#define pb emplace_back
#define vi vector<int>
using namespace std;
const int N=3e5+5;
template<class T>inline void chkmin(T &x,T y) { x=min(x,y); }
template<class T>inline void chkmax(T &x,T y) { x=max(x,y); }
template<class T>inline void read(T &x) {
	T f=1; x=0; char s=getchar();
	while(s<'0'||s>'9'){if(s=='-')f=-1;s=getchar();}
	while(s>='0'&&s<='9'){x=x*10+s-'0';s=getchar();}
	x*=f;
}
struct Edge {
	int u,v,w;
}e[2*N];
vi tr[N];
inline bool cmp(Edge a,Edge b) { return a.w<b.w; }
int n,a[N],m,Q,E[N],lx[N],rx[N],fa[N],ys[N],hfy,A[N];
inline int find(int x) { return (fa[x]==x)?x:fa[x]=find(fa[x]); }
struct Segment_Tree {
	#define ls rt<<1
	#define rs rt<<1|1
	#define mid ((l+r)>>1)
	ll val[N<<2][2][2],sm[N<<2],mn[N<<2];
	inline void init_(int rt,int x) {
		mem(val[rt],0x3f);
		sm[rt]=0; mn[rt]=a[x];
	}
	inline void pu_(int rt,int Mid) {
		sm[rt]=sm[ls]+sm[rs]+E[Mid]; mn[rt]=min(mn[ls],mn[rs]);
		rep(i,0,1) rep(j,0,1) val[rt][i][j]=sm[ls]+sm[rs]+i*mn[ls]+j*mn[rs];
		rep(i,0,1) rep(j,0,1) rep(k,0,1) rep(l,1^k,1) chkmin(val[rt][i][j],val[ls][i][k]+val[rs][l][j]+((k&l)^1)*E[Mid]);
		rep(i,0,1) rep(j,0,1) {
			chkmin(val[rt][i][j],val[ls][i][j]+E[Mid]+sm[rs]);
			chkmin(val[rt][i][1],val[ls][i][j]+E[Mid]+mn[rs]+sm[rs]);
			if(j) chkmin(val[rt][i][0],val[ls][i][j]+sm[rs]),chkmin(val[rt][i][1],val[ls][i][j]+sm[rs]+mn[rs]);
		}
		rep(i,0,1) rep(j,0,1) {
			chkmin(val[rt][i][j],val[rs][i][j]+E[Mid]+sm[ls]);
			chkmin(val[rt][1][j],val[rs][i][j]+E[Mid]+mn[ls]+sm[ls]);
			if(i) chkmin(val[rt][0][j],val[rs][i][j]+sm[ls]),chkmin(val[rt][1][j],val[rs][i][j]+sm[ls]+mn[ls]);
		}
	}
	inline void bld_(int rt,int l,int r) {
		if(l==r) return init_(rt,l);
		bld_(ls,l,mid); bld_(rs,mid+1,r);
		pu_(rt,mid);
	}
	inline void upd_(int rt,int l,int r,int k) {
		if(l==r) return init_(rt,k);
		if(k<=mid) upd_(ls,l,mid,k);
		else upd_(rs,mid+1,r,k);
		pu_(rt,mid);
	}
	#undef ls
	#undef rs
	#undef mid
}t;
map<int,int> ed[N];
inline void dfs(int x,int lst) {
	ys[x]=(++hfy); A[hfy]=a[x];
	for(int v:tr[x]) if(v!=lst) E[hfy]=ed[x][v],dfs(v,x);
}
inline void solve() {
	read(n); read(m);
	rep(i,1,n) read(a[i]),fa[i]=i,lx[i]=rx[i]=i;
	rep(i,1,m) read(e[i].u),read(e[i].v),read(e[i].w);
	rep(i,1,n-1) e[++m].u=i,e[m].v=i+1,e[m].w=(1e9+7);
	sort(e+1,e+m+1,cmp);
	rep(i,1,m) {
		int x=e[i].u,y=e[i].v;
		x=find(x); y=find(y);
		if(x==y) continue;
		ed[rx[y]][lx[x]]=ed[lx[x]][rx[y]]=e[i].w;
		fa[y]=x;
		tr[rx[y]].pb(lx[x]); tr[lx[x]].pb(rx[y]);
		lx[x]=lx[y];
	}
	rep(i,1,n) if(tr[i].size()==1) {
		dfs(i,0);
		break;
	}
	rep(i,1,n) a[i]=A[i];
	t.bld_(1,1,n);
	//rep(i,1,n) printf("%d ",a[i]); puts(""); rep(i,1,n-1) printf("%d ",E[i]); puts("");
	read(Q);
	rep(asd_a,1,Q) {
		int x,y;
		read(x); read(y);
		a[ys[x]]=y;
		t.upd_(1,1,n,ys[x]);
		printf("%lld\n",min(t.val[1][1][1],t.sm[1]+t.mn[1]));
	}
}
int main() {
	//int T; cin>>T; while(T--)
	solve();
}

AUOJ1788

先考虑如何求有多少张曲线图

我们可以设 \(f[i][j]\) 表示考虑完前 \(i\) 个位置,已经有 \(j\) 个位置作为 \(y\) 连边了的方案数

显然对于所有 \(i \ != n \times 2\) 并且 \(i = j \times 2\) 的方案数都为 \(0\)

转移也就是考虑第 \(i\) 个位置是 \(x\) 还是 \(y\) 就行了

接下来我们考虑算贡献

首先 \(x , y\) 的贡献必然要分开算,先考虑算 \(y\)

\(g[i][j]\) 表示考虑完前 \(i\) 个位置,已经有 \(j\) 个位置作为 \(y\) 连边了的总贡献

如果 \(i\)\(x\) 的话,那么 \(g[i][j]=g[i-1][j]\)

如果 \(i\)\(y\) 的话,那么首先,它有 \(i-2 \times j+1\) 个位置可以匹配

\(t=i- 2 \times j + 1\)

那么之前的贡献全部都要乘上 \(t\)

考虑如何计算新增的贡献

我们发现,如果将这 \(t\) 个还未匹配的 \(x\) 排序,那么我们放在第一个位置上它会产生 \(0\) 的贡献,放在第二个位置上会产生 \(1\) 的贡献 \(....\),以此类推,它一共会产生 \(a[i] \times (t \times (t-1) \ / \ 2)\)

那么转移方程也同上面 \(f\) 的转移,并且注意在所有 \(i \ != n \times 2\) 并且 \(i = j \times 2\) 的方案数都为 \(0\)

考虑算 \(x\) 的贡献,实际上就是将序列 \(reverse\) 一下然后同 \(y\)

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

code
#include <bits/stdc++.h>
#define rep(a,b,c) for(int a=(b);a<=(c);++a)
#define per(a,b,c) for(int a=(b);a>=(c);--a)
#define repe(x) for(int yny=head[x],v;yny&&(v=e[yny].v);yny=e[yny].u)
#define ll long long
#define mem(a,b) memset(a,b,sizeof(a))
#define pb emplace_back
#define vi vector<int>
using namespace std;
const int p=998244353;
inline int inc(int x,int y) { if((x+=y)>=p) x-=p; return x; }
inline int dec(int x,int y) { if((x-=y)<0) x+=p; return x; }
inline void Inc(int &x,int y) { x=inc(x,y); }
inline void Dec(int &x,int y) { x=dec(x,y); }
const int N=4005;
int f[N*2][N],g[N*2][N],n,a[N*2],ans;
inline void solve() {
	scanf("%d",&n); n<<=1;
	rep(i,1,n) scanf("%d",&a[i]);
	f[1][0]=1;
	rep(i,2,n) rep(j,0,i/2) {
		f[i][j]=f[i-1][j];
		if(j) Inc(f[i][j],1ll*f[i-1][j-1]*(i-j*2+1)%p);
		if(i!=n&&i==(2*j)) f[i][j]=0;
	}
	rep(i,3,n) rep(j,0,i/2) {
		g[i][j]=g[i-1][j];
		int t=(i-j*2+1);
		if(j) Inc(g[i][j],inc(1ll*g[i-1][j-1]*t%p,1ll*a[i]*f[i-1][j-1]%p*(t*(t-1)/2)%p)%p);
		if(i!=n&&i==(2*j)) g[i][j]=0;
	}
	ans=g[n][n/2];
	reverse(a+1,a+n+1);
	rep(i,3,n) rep(j,0,i/2) {
		g[i][j]=g[i-1][j];
		int t=(i-j*2+1);
		if(j) Inc(g[i][j],inc(1ll*g[i-1][j-1]*t%p,1ll*a[i]*f[i-1][j-1]%p*(t*(t-1)/2)%p)%p);
		if(i!=n&&i==(2*j)) g[i][j]=0;
	}
	Inc(ans,g[n][n/2]);
	printf("%d\n",ans);
}
int main() {
	//int T; cin>>T; while(T--)
	solve();
}

AUOJ1800

有一个非常重要的性质

考虑两条相包含的线段,那么那条长一点的一定要么和短一点的在同一组要么自己单独成一组

考虑用调整的思想证明

如果那条长一点的和短一点的不在同一组,短一点的在的组称其为组 \(A\) ,并且长一点的在另外一个包含多条线段的组称其为组 \(B\)

那么我们考虑将 \(B\) 中的长线段放到 \(A\) 中去,那么因为它包含了短一点的,也就是说它和短一点的取交之后变成了短一点的,对 \(A\) 中的答案无任何影响但是因为 \(B\) 少了一条线段来取交,所以 \(B\) 中的答案一定变得不劣,得证

接下来就简单了,做一个非常显然的观察,如果有一组它们的交是空集的话,这组再加什么线段都没有意义了,我们就可以选前 \(k - 1\) 长的线段放到其它组使它们自成一组

所以答案分为两种情况,有空集和无空集

考虑无空集

这个时候我们已经将包含其他线段的线段去掉了,也就是说剩下的线段必然最多也只是相交,我们将它们按 \(l\) 排序,设 \(f[i][j]\) 表示

考虑了前 \(i\) 条线段,将它们分成了 \(j\) 组的最大的 \(\sum \ 交集\)

容易发现分组必然是连续一段的分最优,于是在一开始先考虑其它多少条线段独成一组,然后直接暴力 \(dp\) 就行了,复杂度 \(O(n^2)\)

code
#include <bits/stdc++.h>
#define rep(a,b,c) for(int a=(b);a<=(c);++a)
#define per(a,b,c) for(int a=(b);a>=(c);--a)
#define ll long long
#define vi vector<int>
#define pb emplace_back
#define pii pair<int,int>
#define fi first
#define se second
#define mp make_pair
#define mem(a,b) memset(a,b,sizeof(a))
using namespace std;
template <typename T> inline void read(T &t) {
	t=0; char ch=getchar(); int f=1;
	while (ch<'0'||ch>'9') { if (ch=='-') f=-1; ch=getchar(); }
	do { (t*=10)+=ch-'0'; ch=getchar(); } while ('0'<=ch&&ch<='9'); t*=f;
}
template<class T>inline void chkmin(T &x,T y) { x=min(x,y); }
template<class T>inline void chkmax(T &x,T y) { x=max(x,y); }
const int p=1e9+7;
inline int inc(int x,int y) { if((x+=y)>=p) x-=p; return x; }
inline int dec(int x,int y) { if((x-=y)<0) x+=p; return x; }
inline void Inc(int &x,int y) { x=inc(x,y); }
inline void Dec(int &x,int y) { x=dec(x,y); }
inline int mval(int x) { if(x<0) return x+p; if(x>=p) x-=p; return x; }
inline int ksm(int a,int b) { int ans=1; for(;b;b>>=1,a=1ll*a*a%p) if(b&1) ans=1ll*ans*a%p; return ans; }
struct line {
	int l,r;
}a[5005],A[5005];
int n,k,len[5005],tag[5005],sz,LEN[5005],SZ,pre[5005][5005];
ll f[5005][5005],ans;
inline void solve() {
	read(n); read(k);
	rep(i,1,n) read(A[i].l),read(A[i].r),len[i]=A[i].r-A[i].l;
	sort(len+1,len+n+1);
	reverse(len+1,len+n+1);
	rep(i,1,k-1) ans+=len[i];
	rep(i,1,n) rep(j,1,n) if(i!=j) {
		if(A[i].l>=A[j].l&&A[i].r<=A[j].r) tag[j]=1;
	}
	rep(i,1,n) if(!tag[i]) a[++sz]=A[i]; else LEN[++SZ]=A[i].r-A[i].l;
	sort(LEN+1,LEN+SZ+1,[](int a,int b){ return a>b; });
	sort(a+1,a+sz+1,[](line a,line b){ return a.l<b.l; });
	mem(f,-0x3f);
	ll sum=0; f[0][0]=0;
	rep(i,1,min(SZ,k)) sum+=LEN[i],f[0][i]=sum;
	rep(i,1,sz) rep(j,1,k) rep(K,1,i) if(a[K].r>a[i].l) chkmax(f[i][j],f[K-1][j-1]+a[K].r-a[i].l);
	printf("%lld\n",max(ans,f[sz][k]));
}
int main() {
	//int T; cin>>T; while(T--)
	solve();
}
posted @ 2021-12-30 10:52  KJGKMTZB  阅读(11)  评论(0)    收藏  举报