P3736 [HAOI2016] 字符合并
有一个长度为 \(n\) 的 \(01\) 串,你可以每次将相邻的 \(k\) 个字符合并,得到一个新的字符并获得一定分数。
得到的新字符和分数由这 \(k\) 个字符确定。你需要求出你能获得的最大分数。
-
\(1≤n≤300\),\(1<k≤8\)。
-
\(c_i∈{0,1}\),\(1≤w_i≤10^9\)。
对于最大分数,则尽量合并更多的字符,考虑\(k\le8\)可以对\(k\)状压,设计\(f_{l,r,t}\)为区间\([l,r]\)中最终被合并的状态是\(t\)的最大分数,易得这些区间不会相交,则枚举中间的断点\(mid\in [l,r]\),打表很明显能够发现当串的长度为\(a(k-1)+1\)时该串最后生成串的长度一定为\(1\),则当串的长度为\(len\)时,我们考虑\(S\)的最后一位是怎么来的,最后该串的长度为\((len-1) \mod {(k-1)}+1\)
则转移如下:
-
\(dp[i][j][S]= dp[i][mid][S>>1] + dp[mid+1][j][S\&1]\)
-
对于长度为\(1\)的区间单独讨论
c++
#include"bits/stdc++.h"
using namespace std;
const int N = 310,K = 8;
#define inl inline
#define reg register
#define regi register int
#define PII pair
#define ll long long
const int INF = 1e9 + 10;
ll n,k,a[N],c[1 << K],w[1 << K],f[N][N][1 << K];
inl ll read(void)
{ll x = 0,f = 1;char ch = getchar();while(!isdigit(ch)) f = ch == '-' ? - 1 : f,ch = getchar();while(isdigit(ch)) x = (x << 3) + (x << 1) + ch - '0',ch = getchar();return x * f;
}
int main(void)
{n = read(),k = read();for(regi i = 1;i <= n;i ++) a[i] = read();for(regi i = 0;i < (1 << k);i ++) c[i] = read(),w[i] = read();for (regi i = 1;i <= n;i ++)for (regi j = 1;j <= n;j ++)for (regi z = 0;z < (1 << k);z ++)f[i][j][z] = - INF;for(regi i = n;i >= 1;i --){for(regi j = i;j <= n;j ++){if(i == j){f[i][j][a[i]] = 0;continue;}ll len = j - i;len %= k - 1;if(!len) len = k - 1;for(regi mid = j;mid > i;mid -= k - 1){for(regi S = 0;S < (1 << len);S ++){f[i][j][S << 1] = max(f[i][j][S << 1],f[i][mid - 1][S] + f[mid][j][0]);f[i][j][S << 1 | 1] = max(f[i][j][S << 1 | 1],f[i][mid - 1][S] + f[mid][j][1]);}}if(len == k - 1){ll g[2];g[0] = g[1] = - INF;for(regi S = 0;S < (1 << k);S ++) g[c[S]] = max(g[c[S]],f[i][j][S] + w[S]);f[i][j][0] = g[0],f[i][j][1] = g[1];}}}ll ans = - INF;for(regi i = 0;i < (1 << k);i ++) ans = max(ans,f[1][n][i]);printf("%lld",ans);return 0;
}
P5336 [THUSC2016] 成绩单
就是给出一个序列 \(a[1...n]\) 还有\(A\)和\(B\)
每次从序列里抽出连续的一段,这一段的代价为
\(A+B∗(max−min)^2\)
\(n≤50,a≤1500,b≤10,w_i≤1000。\)
设 \(f_{l,r}\)为区间\([l,r]\)成绩单全部被发放所需要的最小代价,\(g_{l,r,mn,mx}\)为区间\([l,r]\)最小值\(mn\)最大值\(mx\)最小代价
易得 \(f_{l,r}=min{\{g_{l,r,mn,mx}+a+b\times(mx-mn)^2\}}\)
1.保留第\(r+1\)张成绩单不取走,未来与 \([l,r]\) 中的成绩单共同取走: \(g_{l,r+1,min(mn,w[r+1]),max(mx.w[r+1])}=min(g_{l,r,mn,mx})\)
- 2.枚举 \(k\),将 \([r+1,k]\) 这段区间的成绩单共同取走
\(g_{l,k,mn,mx}=min(g_{l,r,mn,mx}+f_{r+1,k})\)
可以转化为:
\(g_{l,r,mn,mx}=min(g_{l,k,mn,mx}+f_{k+1,r})\)
c++
#include"bits/stdc++.h"
using namespace std;
const int N = 60;
#define inl inline
#define reg register
#define regi register int
#define PII pair
//#define ll long long
int n,a,b,w[N],f[N][N],g[N][N][N][N],d[N];
int tot;
inl int read(void)
{int x = 0,f = 1;char ch = getchar();while(!isdigit(ch)) f = ch == '-' ? - 1 : f,ch = getchar();while(isdigit(ch)) x = (x << 3) + (x << 1) + ch - '0',ch = getchar();return x * f;
}
int main(void)
{n = read(),a = read(),b = read();for(regi i = 1;i <= n;i ++) w[i] = read(),d[i] = w[i];sort(d + 1, d + n + 1);tot = unique(d + 1,d + n + 1) - d - 1;for(regi i = 1;i <= n;i ++) w[i] = lower_bound(d + 1,d + tot + 1,w[i]) - d,f[i][i] = a;memset(g,0x3f,sizeof g);memset(f,0x3f,sizeof f);for(regi i = 1;i <= n;i ++) g[i][i][w[i]][w[i]] = 0;for(regi len = 1;len <= n;len ++)for(regi l = 1,r = len;r <= n;l ++,r ++)for(regi mn = 1;mn <= tot;mn ++){for(regi mx = mn;mx <= tot;mx ++){for(regi k = l;k < r;k ++) g[l][r][mn][mx] = min(g[l][k][mn][mx] + f[k + 1][r],g[l][r][mn][mx]);g[l][r + 1][min(mn,w[r + 1])][max(mx,w[r + 1])] = min(g[l][r + 1][min(mn,w[r + 1])][max(mx,w[r + 1])],g[l][r][mn][mx]);f[l][r] = min(f[l][r],g[l][r][mn][mx] + a + b * (d[mx] - d[mn]) * (d[mx] - d[mn]));}}printf("%d",f[1][n]);return 0;
}
P3349 [ZJOI2016] 小星星
抽象化题意:
一个\(n\)个节点的树,和一个\(n\)个节点的图,要求给树上的每个节点编号,使得编号是一个\(1\)到\(n\)的排列,并且要满足树上任意一条边\((u,v)\),图中一定要有边\((x_u,x_v)\)(\(x_u\)表示点\(u\)的编号),求方案数。
\(n≤17\),\(m≤\frac{1}{2}n(n-1)\)。
考虑暴力:设\(f_{i,j,S}\)为节点\(i\)编号为\(j\),\(i\)的子树内的编号集合为\(S\)的方案数。这样转移时间复杂度为\(O(n^3\times3^n)\)肯定过不去
考虑去掉\(S\)这一维\(f_{i,j}\)为节点\(i\)编号为\(j\)的方案数
如果出现重复的编号,考虑容斥
\(2^n\)枚举\(\{1,2,3...n\}\)的一个子集\(S\),显然容斥系数是\((-1)^{n-|S|}\)
c++
#include"bits/stdc++.h"
using namespace std;
const int N = 20;
#define inl inline
#define reg register
#define regi register int
#define PII pair
#define ll long long
ll n,m,tot,f[N],exi[N];
ll dp[N][N],ans;
vector ver[N];
struct node
{ll v,net;
}edge[N << 1];
inl ll read(void)
{ll x = 0,f = 1;char ch = getchar();while(!isdigit(ch)) f = ch == '-' ? - 1 : f,ch = getchar();while(isdigit(ch)) x = (x << 3) + (x << 1) + ch - '0',ch = getchar();return x * f;
}
inl void dfs(int u,int fa)
{for(regi i = 1;i <= n;i ++) dp[u][i] = 1;for(regi i = f[u];i;i = edge[i].net){int v = edge[i].v;if(v == fa) continue;dfs(v,u);for(regi j = 1;j <= n;j ++){ll s = 0;for(regi l = 0;l < ver[j].size();l ++){int k = ver[j][l];s += dp[v][k] * (exi[j] & exi[k]);}dp[u][j] *= s;}}
}
int main(void)
{n = read(),m = read();for(regi i = 1;i <= m;i ++){int u = read(),v = read();ver[u].push_back(v);ver[v].push_back(u);}for(regi i = 2;i <= n;i ++){int u = read(),v = read();edge[++ tot] = node{v,f[u]},f[u] = tot;edge[++ tot] = node{u,f[v]},f[v] = tot;}for(regi i = 0;i < (1 << n);i ++){int x = n;memset(dp,0,sizeof dp);memset(exi,0,sizeof exi);for(regi j = 1;j <= n;j ++) if(i & (1 << j - 1)) exi[j] = 1,x --;dfs(1,0);for(regi j = 1;j <= n;j ++) ans += dp[1][j] * (x % 2 ? - 1 : 1);}printf("%lld",ans);return 0;
}
P4516 [JSOI2018] 潜入行动
给一棵 \(n\) 个点的树,试选取 \(k\) 个点,使得树中的每一个点都至少与该 \(k\) 个点中的一个点直接相连。( \(k\) 个点中的点不与自身相连 )
\(n≤10^5,k≤100\)
很明显,要跑背包
\(dp[x][i][0/1][0/1]\)表示以\(x\)为根的子树中共放了\(i\)个监听装置,其中\(x\)点放没放装置,\(x\)点有没有被监听到的方案数
\(dp[x][i+j][0][0]=∑dp[x][i][0][0]∗dp[v][j][0][1]\)
\(dp[x][i+j][1][0]=∑dp[x][i][1][0]∗(dp[v][j][0][0]+dp[v][j][0][1])\)
\(dp[x][i+j][0][1]=∑(dp[x][i][0][1]∗(dp[v][j][0][1]+dp[v][j][1][1])+dp[x][i][0][0]∗dp[v][j][1][1])\)
\(dp[x][i+j][1][1]=∑(dp[x][i][1][0]∗(dp[v][j][1][0]+dp[v][j][1][1])+dp[x][i][1][1]∗(dp[v][j][0][0]+dp[v][j][0][1]+dp[v][j][1][0]+dp[v][j][1][1]))\)
c++
#include"bits/stdc++.h"
using namespace std;
#define inl inline
#define reg register
#define regi register int
#define ll long long
#define PII pair
const int N=100015,P=1e9+7,M=101;
inl int read(void)
{int x=0,f=1;char ch=getchar();while(!isdigit(ch)) f=ch=='-'?-1:f,ch=getchar();while(isdigit(ch)) x=(x<<3)+(x<<1)+ch-'0',ch=getchar();return x*f;
}
int n,K;
int f[N][M][2][2],tmp[M][2][2];
int sz[N];
int ver[N<<1],net[N<<1],cnt,head[N];
void addedge(int u,int v){ver[++cnt]=v,net[cnt]=head[u],head[u]=cnt;}
void dfs(int u,int fa)
{sz[u]=1,f[u][0][0][0]=f[u][1][1][0]=1;for(int i=head[u];i;i=net[i]){int v=ver[i];if(v==fa) continue;dfs(v,u);for(int j=0;j<=sz[u]&&j<=K;j++)for(int k=0;k<=sz[v]&&j+k<=K;k++){if(f[u][j][0][1])(tmp[j+k][0][1]+=1ll*f[u][j][0][1]*(f[v][k][0][1]+f[v][k][1][1])%P)%=P;if(f[u][j][1][0])(tmp[j+k][1][0]+=1ll*f[u][j][1][0]*(f[v][k][0][0]+f[v][k][0][1])%P)%=P,(tmp[j+k][1][1]+=1ll*f[u][j][1][0]*(f[v][k][1][0]+f[v][k][1][1])%P)%=P;if(f[u][j][0][0])(tmp[j+k][0][0]+=1ll*f[u][j][0][0]*f[v][k][0][1]%P)%=P,(tmp[j+k][0][1]+=1ll*f[u][j][0][0]*f[v][k][1][1]%P)%=P;if(f[u][j][1][1]){int res=0;(res+=f[v][k][0][0])%=P,(res+=f[v][k][0][1])%=P,(res+=f[v][k][1][0])%=P,(res+=f[v][k][1][1])%=P,(tmp[j+k][1][1]+=1ll*f[u][j][1][1]*res%P)%=P;}}sz[u]+=sz[v];for(int j=0;j<=sz[u]&&j<=K;j++)f[u][j][0][0]=tmp[j][0][0],tmp[j][0][0]=0,f[u][j][0][1]=tmp[j][0][1],tmp[j][0][1]=0,f[u][j][1][0]=tmp[j][1][0],tmp[j][1][0]=0,f[u][j][1][1]=tmp[j][1][1],tmp[j][1][1]=0;}
}
signed main(void)
{n=read(),K=read();for(int i=1,u,v;i<=n-1;i++) u=read(),v=read(),addedge(u,v),addedge(v,u);dfs(1,0);int ans=(f[1][K][0][1]+f[1][K][1][1])%P;printf("%d",ans);return 0;
}
CF1442D Sum
给定 \(n\) 个不降的数组。有一个值 \(ans\),初始为 \(0\)。你需要进行如下操作 \(k\) 次:
- 选择一个数组,把 \(ans\) 加上数组的第一个元素,之后把它删除。
请求出 \(ans\) 最大是多少。
所有数组的元素总个数 \(\leq 10^6\),\(n,k\leq 3000\)。
对于所有序列,显然选部分的序列个数 \(≤1\),其它全选或全不选。
有了上述结论,我们只要枚举哪个选部分,其余数列当做 \(01\) 背包问题即可。
-
对于区间 \([l,r]\),找到一个中间点 \(mid\)。
-
先将 \([l,mid]\) 中的物品插入到背包中,然后 \(solve(mid+1,r)\)。
-
将背包还原,再将 \([mid+1,r]\) 中的物品插入到背包中,然后 \(solve(l,mid)\)。
-
这样一来,如果当前的区间是 \([l,r]\),可以发现此时背包中的物品恰为 \([1,l−1]∪[r+1,n]\) 中的物品!
-
因此,当 \(l=r\) 时我们得到的就是想要的答案。
时间复杂度为\(O(knlogn)\)轻松过
c++
#include"bits/stdc++.h"
using namespace std;
const int N = 3010;
#define inl inline
#define reg register
#define regi register int
#define PII pair
#define int long long
using namespace std;
inl int read(void)
{int x = 0,f = 1;char ch = getchar();while(!isdigit(ch)) f = ch == '-' ? - 1 : f,ch = getchar();while(isdigit(ch)) x = (x << 3) + (x << 1) + ch - '0',ch = getchar();return x * f;
}
int n,k,x,t[N],sum[N],ans,dp[N];
vector a[N];
void solve(int l,int r)
{int mid = (l + r) >> 1;if(l == r){int now = 0;for(int i = 0;i <= min(t[l],k);i ++){ans = max(ans,now + dp[k - i]);now += a[l][i];}return;}for(regi i = mid + 1;i <= r;i ++)for(regi j = k;j >= t[i];j --)dp[j] = max(dp[j],dp[j - t[i]] + sum[i]);solve(l,mid);for(regi i = 0;i <= k;i ++) dp[i] = f[i];for(regi i = l;i <= mid;i ++)for(regi j = k;j >= t[i];j --)dp[j] = max(dp[j],dp[j - t[i]] + sum[i]);solve(mid + 1,r);
}
signed main(void)
{n = read(),k = read();for(regi i = 1;i <= n;i ++){t[i] = read();for(regi j = 1,tmp;j <= t[i];j ++){tmp = read();a[i].push_back(tmp);sum[i] += tmp;}}solve(1,n);printf("%lld",ans);return 0;
}
P6773 [NOI2020] 命运
题意
给定一棵 \(n\) 个点的树和 \(m\) 条限制,你可以给树上的每一条边赋一个 \(0\) 或 \(1\) 的权值。对于所有限制 \((u,v)\)(保证 \(v\) 为 \(u\) 的祖先) 你需要保证 \(u\) 到 \(v\) 上至少有一条边的权值为 \(1\),求赋值方案数。
Data Range:\(1≤n,m≤5×10^5\)
设\(f_{i,j}\)表示\(i\)往上第一条权值为\(1\)的边的深度为\(j\)时,子树的方案数,那么对于每一条限制\((x=>y)\),相当于令\(y\)的\(f_{y,p}=0(p\in[dep_x,n])\),不妨记\(lim_i\)表示\(i\)往上最远的权值为\(1\)的边至多深度为多浅
转移时,考虑自己与儿子之间的边为\(1\)还是\(0\),可以得到:
用线段树合并来优化,在计算贡献之前令所有儿子的\(f_{y,j}\)加上\(f_{y,dep_y}\),贡献时恰好是让自己的第\(j\)位贡献父亲的第\(j\)位,这完全是对应的,用线段树维护第二维,维护区间加和区间乘
c++
#include"bits/stdc++.h"
using namespace std;
const int N=5e5+15,P=998244353;
#define inl inline
#define reg register
#define regi register int
#define PII pair
#define ll long long
#define int long long
inl int read(void)
{int x=0,f=1;char ch=getchar();while(!isdigit(ch)) f=ch=='-'?-1:f,ch=getchar();while(isdigit(ch)) x=(x<<3)+(x<<1)+ch-'0',ch=getchar();return x*f;
}
vector v[N];
int n,m;
void mod(ll &x){x%=P;}
struct node
{int ls,rs;int s,mul;#define mul(p) tr[p].mul#define ls(p) tr[p].ls#define rs(p) tr[p].rs#define s(p) tr[p].s
}tr[N<<6];
void pushdown(int p)
{if(ls(p))s(ls(p))=s(ls(p))*mul(p)%P,mul(ls(p))=mul(ls(p))*mul(p)%P;if(rs(p))s(rs(p))=s(rs(p))*mul(p)%P,mul(rs(p))=mul(rs(p))*mul(p)%P;mul(p)=1;
}
int head[N],ver[N<<1],net[N<<1],cnt,dep[N];
void addedge(int a,int b)
{ver[++cnt]=b,net[cnt]=head[a],head[a]=cnt;
}
ll query(int p,int l,int r,int x)
{if(!p||r<=x) return s(p);int mid=l+r>>1;ll s=0;pushdown(p);if(mid<=x-1) mod(s+=query(rs(p),mid+1,r,x));mod(s+=query(ls(p),l,mid,x));return s;
}
int tot;
void change(int &p,int l,int r,int x)
{p=++tot,s(p)=mul(p)=1;if(l==r) return ;int mid=l+r>>1;if(x<=mid) change(ls(p),l,mid,x);else change(rs(p),mid+1,r,x);
}
int merge(int x,int y,int l,int r,ll &s1,ll &s2)
{if(!x&&!y) return 0;if(!x||!y){if(!x){mod(s1+=s(y));mul(y)=mul(y)*s2%P,s(y)=s(y)*s2%P;return y;}mod(s2+=s(x));s(x)=s(x)*s1%P,mul(x)=mul(x)*s1%P;return x;}if(l==r){ll tx=s(x),ty=s(y);mod(s1+=ty);s(x)=(s(x)*s1+s(y)*s2)%P;mod(s2+=tx);return x;}pushdown(x),pushdown(y);int mid=l+r>>1;ls(x)=merge(ls(x),ls(y),l,mid,s1,s2);rs(x)=merge(rs(x),rs(y),mid+1,r,s1,s2);s(x)=(s(ls(x))+s(rs(x)))%P;return x;
}
int boring[N];
void dfs(int x,int fa)
{dep[x]=dep[fa]+1;int res=0;for(auto t:v[x]) res=max(res,dep[t]);change(boring[x],0,n,res);for(int i=head[x];i;i=net[i]){int y=ver[i];if(y==fa) continue;dfs(y,x);ll S=query(boring[y],0,n,dep[x]),S_=0;boring[x]=merge(boring[x],boring[y],0,n,S,S_);}
}
signed main(void)
{n=read();for(int i=1,u,v;i<=n-1;i++)u=read(),v=read(),addedge(u,v),addedge(v,u);m=read();for(int i=1,x,y;i<=m;i++)x=read(),y=read(),v[y].push_back(x);dfs(1,0);printf("%lld",query(boring[1],0,n,0));return 0;
}
P4590 [TJOI2018] 游园会
题目大意
令 \(f(i)\) 为满足下面几条限制的字符串个数:
- 长度为 \(n\),字符集为 \({N,O,I}\)。
- 不包含子串 \(NOI\)。
- 与模式串 \(s\) 的最长公共子序列\((lcs)\)长度为 \(i\)。
对于 \(i∈[0,k]\),求出 \(f_i\) 的值,对 \(10^9+7\) 取模。
\(DP\) 套 \(DP\) 板子题
所谓 \(dp\) 套 \(dp\) ,实际上就是在说求解一个 \(dp\) 的过程中,我们用另一个 \(dp\) 求解出他应该从某个状态转移到另一个状态。
设求 \(LCS\) 的 \(dp:\) \(dp_{i,j}=max{\{dp_{i-1,j},dp_{i,j-1},dp_{i-1,j-1}+[s_i=t_i]\}}\)
显然当\(i\)一定时,\(dp_{i,j}\)时单调不降的,且相邻两个数的差\(\in[0,1]\),通过状压把 \(LCS\) 数组的状态设到转移方程里
设\(f_{i,S,0/1/2}\)表示当考虑了串的前\(i\)位的时候,\(LCS\)的\(dp\)式子的状态为\(S\),最后几位同"\(NOI\)"的匹配到了第\(k\)位\((k\in[0,2])\),此时的方案数
设\(sta_{S,j}\)表示\(LCS\)的\(dp\)式子在状态为\(S\)时新加入一位\(j\)后的\(dp\)式子状压下来的情况,设\(nxt_{x,y}\)表示下一个"\(NOI\)"匹配的状态,初始状态\(f_{0,0,0}=1\),
时间复杂度为\(O(n2^k+k2^k)\)
c++
#include"bits/stdc++.h"
using namespace std;
const int N = 1010,mod = 1e9 + 7;
#define inl inline
#define reg register
#define regi register int
#define PII pair
//#define ll long long
inl int read(void)
{int x = 0,f = 1;char ch = getchar();while(!isdigit(ch)) f = ch == '-' ? - 1 : f,ch = getchar();while(isdigit(ch)) x = (x << 3) + (x << 1) + ch - '0',ch = getchar();return x * f;
}
int n,m,f[2][N],a[N],b[N];
int nxt[3][3]={{1,0,0},{1,2,0},{1,0,3}};
int dp[5][100010][5],sta[100010][5];
int ans[N],now;
char ch[N];
inl void init()
{for(int p = 0;p < (1 << m);p ++){for(int k = 0;k < m;k ++) f[0][k + 1] = f[0][k] + ((p >> k) & 1);for(int j = 0;j <= 2;j ++){for(int k = 1;k <= m;k ++){f[1][k] = max(f[1][k - 1],f[0][k]);if(b[k] == j) f[1][k] = max(f[1][k],f[0][k - 1] + 1);}int t = 0;for(int i = 1;i <= m;i ++) if(f[1][i] > f[1][i - 1]) t |= (1 << (i - 1));sta[p][j] = t;}}
}
int main(void)
{n = read(),m = read();cin >> (ch + 1);for(int i = 1;i <= m;i ++){if(ch[i] == 'N') b[i]=0;if(ch[i] == 'O') b[i]=1;if(ch[i] == 'I') b[i]=2;}init();dp[0][0][0] = 1;for(regi i = 1;i <= n;i ++){memset(dp[now ^ 1],0,sizeof(dp[now ^ 1]));for(regi p = 0;p < (1 << m);p ++){for(regi j = 0;j <= 2;j ++){for(regi k = 0;k <= 2;k ++){if(j == 2 && k == 2) continue;int fa = nxt[j][k];dp[now ^ 1][sta[p][k]][fa] += dp[now][p][j];dp[now ^ 1][sta[p][k]][fa] %= mod;}} }now ^= 1;}for(regi p = 0;p < (1 << m);p ++){for(regi j = 0;j <= 2;j ++){int fa = 0,le = p;while(le){if(le %2 == 1) fa ++;le /= 2;}ans[fa] += dp[now][p][j];ans[fa] %= mod;}}for(regi i = 0;i <= m;i++) printf("%d\n",ans[i]);return 0;
}