USACO19DEC P

Greedy Pie Eaters P

\(m\) 头奶牛,\(n\) 个派。

选择一个奶牛序列 \(\{c_k\}\),从 \(1\)\(k\),奶牛 \(c_i\) 会吃掉 \([l_i,r_i]\) 的所有派(\([l_i,r_i]\) 不能已经全部吃完)。

\(\sum w_{c_i}\) 的最大值。

\(n\le 300\)\(m\le \frac{n(n-1)}{2}\)\(1\le w_i\le 10^6\)\([l_i,r_i]\) 互不相同。


\(f_{l,r}\) 为吃了 \([l,r]\)(不一定吃完)的 \((\sum w)_{\max}\)

那么若存在 \(l\le l_i\le k\le r_i\le r\),有 \(f_{l,r}\leftarrow f_{l,k-1}+f_{k+1,r}+w_i\)

也有 \(f_{l,r}\leftarrow f_{l,k}+f_{k+1,r}\)。时间复杂度 \(O(n^3m)\)

考虑优化 \(O(m)\) 枚举的过程,记 \(g_{k,l,r}\) 为满足转移条件的 \(w_{\max}\)

对于 \((w,l,r)\),对 \(k\in[l,r]\),令 \(g_{k,l,r}\)\(w\)\(\max\),可以区间 DP 转移。

时间复杂度 \(O(nm+n^3)\)

#include<bits/stdc++.h>
#define N 305
using namespace std;
int read(){
	int x=0,w=1;char ch=getchar();
	while(!isdigit(ch)){if(ch=='-')w=-1;ch=getchar();}
	while(isdigit(ch))x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
	return x*w;
}
int n,m;
int f[N][N],g[N][N][N];
void chkmax(int &x,int y){if(x<y)x=y;}
int main(){
	n=read(),m=read();
	for(int i=1,w,l,r;i<=m;i++){
		w=read(),l=read(),r=read();
		for(int k=l;k<=r;k++)
			chkmax(g[k][l][r],w);
	}
	for(int k=1;k<=n;k++)
		for(int len=1;len<=n;len++)
			for(int l=1,r=len;r<=n;l++,r++){
				if(l+1<=k&&k<=r)chkmax(g[k][l][r],g[k][l+1][r]);
				if(l<=k&&k<=r-1)chkmax(g[k][l][r],g[k][l][r-1]);
			}
	for(int len=1;len<=n;len++)
		for(int l=1,r=len;r<=n;l++,r++)
			for(int k=l;k<=r;k++){
				chkmax(f[l][r],f[l][k]+f[k+1][r]);
				chkmax(f[l][r],f[l][k-1]+f[k+1][r]+g[k][l][r]);
			}
	printf("%d\n",f[1][n]);
	
	return 0;
}

Bessie's Snow Cow P

维护树上的若干不重集,支持:

  • 子树插入 \(x\)
  • 子树集合大小求和

\(1\le n,q,c\le 10^5\)


对每种颜色开一个 set 存有值的区间,用一颗线段树统计答案。

注意到只有 \(n\)​ 个区间,且这些区间要么包含要么不交。

于是遇到某个节点的祖先可以将其暴力删除,区间总数线性。

注意处理区间已被覆盖的情况。

#include<bits/stdc++.h>
#define ll long long
#define N 100010
#define sit set<node>::iterator
using namespace std;
int read(){
	int x=0,w=1;char ch=getchar();
	while(!isdigit(ch)){if(ch=='-')w=-1;ch=getchar();}
	while(isdigit(ch))x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
	return x*w;
}
struct Tree{
	ll s[N<<2],t[N<<2];
	#define ls p<<1
	#define rs p<<1|1
	#define mid ((l+r)>>1)
	void pushup(int p){
		s[p]=s[ls]+s[rs];
	}
	void spread(int p,int f,int l,int r){
		s[p]+=t[f]*(r-l+1),t[p]+=t[f];
	}
	void pushdown(int p,int l,int r){
		spread(ls,p,l,mid);
		spread(rs,p,mid+1,r);
		t[p]=0;
	}
	void modify(int p,int l,int r,int L,int R,int k){
		if(L<=l&&r<=R)
			return s[p]+=k*(r-l+1),t[p]+=k,void();
		pushdown(p,l,r);
		if(L<=mid)modify(ls,l,mid,L,R,k);
		if(R>mid)modify(rs,mid+1,r,L,R,k);
		pushup(p);
	}
	ll query(int p,int l,int r,int L,int R){
		if(L<=l&&r<=R)return s[p];
		pushdown(p,l,r);
		ll ret=0;
		if(L<=mid)ret=query(ls,l,mid,L,R);
		if(R>mid)ret+=query(rs,mid+1,r,L,R);
		return ret;
	}
}T;
struct node{
	int l,r;
	node(int _l=0,int _r=0):l(_l),r(_r){}
	bool operator<(const node &t)const{return l<t.l;}
};
int n,q;
struct Merge{
	set<node>s;
	void modify(int l,int r){
		sit it1=s.lower_bound(node(l+1));
		sit it2=s.lower_bound(node(r+1));
		for(sit it=it1;it!=it2;it++)
			T.modify(1,1,n,it->l,it->r,-1);
		int prer=(it1==s.begin())?-1:prev(it1)->r;
		s.erase(it1,it2);
		if(prer<r){
			s.insert(node(l,r));
			T.modify(1,1,n,l,r,1);
		}
	}
}B[N];
vector<int>e[N];
int st[N],ed[N],tim;
void dfs(int u,int f){
	st[u]=++tim;
	for(int v:e[u])
		if(v!=f)dfs(v,u);
	ed[u]=tim;
}
int main(){
	n=read(),q=read();
	for(int i=1,u,v;i<n;i++){
		u=read(),v=read();
		e[u].push_back(v),e[v].push_back(u);
	}
	dfs(1,0);
	for(int opt,x;q;q--){
		opt=read(),x=read();
		if(opt==1)B[read()].modify(st[x],ed[x]);
		if(opt==2)printf("%lld\n",T.query(1,1,n,st[x],ed[x]));
	}
	
	return 0;
}

Tree Depth P

给定 \(n,k\),对于 \(i\in[1,n]\)

\[\sum_{\pi}\operatorname{depth}(\pi,i) \]

其中 \(\pi\) 为长为 \(n\) 且逆序对数为 \(k\) 的排列,\(\operatorname{depth}(\pi,i)\) 为排列 \(\pi\) 构建出的(小根)笛卡尔树上 \(i\) 的深度。

\(n\le 300\)


用一个好的方法处理这个深度:\(\displaystyle \sum_{v}[\operatorname{lca}(u,v)=v]\),这个式子是不受树的形态的影响的。

\(\operatorname{lca}(u,v)=v\) 意味着 \(a_v\)\([\min(u,v),\max(u,v)]\)\(a_i\) 的最小值。

枚举 \(u,v\),即求,有多少排列 \(\pi\),满足逆序对数为 \(k\)\(\displaystyle a_v=\min_{i=\min(u,v)}^{\max(u,v)}a_i\)

\(f_{i,j}\) 表示长为 \(i\),逆序对数为 \(j\) 的排列数,可以直接前缀和转移,第 \(i\) 个填的数可以产生 \([0,i-1]\) 的逆序对数。

然后根据 \(u,v\) 的关系来决定填数顺序。

  • \(u\le v\),按照 \(u\sim v,u-1\sim 1,v+1\sim n\) 的顺序填数,\(v\) 处产生 \(v-u\) 个逆序对,用 OGF 刻画答案:

\[[x^k]x^{v-u}\cdot \prod_{i=0}^{|u-v|-1}(\sum_{j=0}^{i}x^j)\cdot \prod_{i=|u-v|+1}^{n-1}(\sum_{j=0}^{i}x^j) \]

  • \(u>v\),按照 \(u\sim v,u+1\sim n,v-1\sim 1\) 的顺序填数,\(v\) 处不产生逆序对,同理可得

\[[x^k]\prod_{i=0}^{|u-v|-1}(\sum_{j=0}^{i}x^j)\cdot \prod_{i=|u-v|+1}^{n-1}(\sum_{j=0}^{i}x^j) \]

注意到乘积式只与 \(|u-v|\) 有关,处理前后缀可以做到 \(O(n^3)\)

#include<bits/stdc++.h>
#define N 305
#define M 45000
using namespace std;
int read(){
	int x=0,w=1;char ch=getchar();
	while(!isdigit(ch)){if(ch=='-')w=-1;ch=getchar();}
	while(isdigit(ch))x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
	return x*w;
}
int n,k,P;
int pre[N][M],suf[N][M];
int sum[M],c1[N],c2[N];
int qsum(int l,int r){
	return (sum[r]-(l>0?sum[l-1]:0)+P)%P;
}
int main(){
	n=read(),k=read(),P=read();
	pre[0][0]=1;
	for(int i=0;i<=k;i++)sum[i]=1;
	for(int i=1;i<n;i++){
		for(int j=0;j<=k;j++)pre[i][j]=qsum(j-i,j);
		sum[0]=pre[i][0];
		for(int j=1;j<=k;j++)
			sum[j]=(sum[j-1]+pre[i][j])%P;
	}
	suf[n][0]=1;
	for(int i=0;i<=k;i++)sum[i]=1;
	for(int i=n-1;~i;i--){
		for(int j=0;j<=k;j++)suf[i][j]=qsum(j-i,j);
		sum[0]=suf[i][0];
		for(int j=1;j<=k;j++)
			sum[j]=(sum[j-1]+suf[i][j])%P;
	}
	c1[0]=c2[0]=pre[n-1][k];
	for(int i=1;i<n;i++){
		for(int j=0;j<=k;j++)
			c1[i]=(c1[i]+1ll*pre[i-1][j]*suf[i+1][k-j])%P;
		for(int j=0;j<=k-i;j++)
			c2[i]=(c2[i]+1ll*pre[i-1][j]*suf[i+1][k-i-j])%P;
	}
	for(int i=1;i<=n;i++){
		int ans=0;
		for(int j=1;j<=n;j++){
			if(j<=i)(ans+=c1[i-j])%=P;
			else (ans+=c2[j-i])%=P;
		}
		printf("%d ",ans);
	}
	printf("\n");

	return 0;
}
posted @ 2024-02-15 20:10  SError  阅读(5)  评论(0编辑  收藏  举报