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]\) 求
其中 \(\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 刻画答案:
- 若 \(u>v\),按照 \(u\sim v,u+1\sim n,v-1\sim 1\) 的顺序填数,\(v\) 处不产生逆序对,同理可得
注意到乘积式只与 \(|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;
}