周一
最近补一补icpc网络赛的题目
C Delete the Tree
签到题,但是当时卡了一下
首先每次操作都是删一个点,那么要删完肯定是n次操作。
所以就是让删除操作尽可能少。
可以发现,度数为1的点不能执行2操作,必须执行1操作,所以度数为1的点的个数是答案的下界。
那么有达到这个下界的做法呢?度数为1的点只有两种情况,一种是叶子,一种是根节点然后它只有一个儿子。
我们可以从叶子开始,删叶子使得叶子的父亲度数为2,然后执行2操作把父亲删掉,然后叶子就有新的父亲,然后继续删叶子使得父亲度数为2,一直重复,这样可以删完所有的点。
一开始WA了一发,后面注意到数据范围还有n=1的情况,这时度数为0。
以后写题时要注意数据范围里面n特别小的情况,很可能要特判
#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
const int N = 1e6 + 10;
int d[N], n;
int main()
{
int T; scanf("%d", &T);
while(T--)
{
scanf("%d", &n);
_for(i, 1, n) d[i] = 0;
_for(i, 1, n - 1)
{
int u, v;
scanf("%d%d", &u, &v);
d[u]++; d[v]++;
}
int ans = 0;
_for(i, 1, n)
if(d[i] <= 1)
ans++;
printf("%d\n", ans);
}
return 0;
}
D Find the Number(打表)
当时就想着打表,可以算出最大是C(20, 10),当时把阶乘写出来感觉很大没去算,其实算一下挺小的。所以可以直接爆搜打表。
#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
set<int> s;
void dfs(int x, int num, int pos)
{
if(num == 0)
{
if(x <= 1e9) s.insert(x);
return;
}
if(pos == 30) return;
dfs(x | 1 << pos, num - 1, pos + 1);
dfs(x, num, pos + 1);
}
int main()
{
_for(i, 1, 16)
dfs(1 << i, i - 1, i + 1);
int T; scanf("%d", &T);
while(T--)
{
int l, r;
scanf("%d%d", &l, &r);
auto it = s.lower_bound(l);
if(it != s.end() && *it <= r) printf("%d\n", *it);
else puts("-1");
}
return 0;
}
G Read the Documentation(dp)
还是需要多练习dp,dp在比赛中非常常见
充分利用n小,最长只有4段的条件
用dp[i][p1][p2][p3][p4]状态的最优解,其实想到这个状态下面就很简单了
限制一下p1 p2 p3 p4的上界,然后由于最多往前i-5,i到i-5有6个,所以滚动数组将下标模6
然后因为中间要空一格,所以前一个状态可能到-1,而-1却是合法的,这时要处理一下。
#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
typedef long long ll;
const int N = 110;
ll dp[6][55][35][27][22], k[5], x[5], s[N];
int n, T;
int main()
{
scanf("%d%d", &n, &T);
_for(i, 1, 4)
{
scanf("%lld", &x[i]);
k[i] = k[i - 1] + x[i];
}
_for(i, 1, n)
{
ll x; scanf("%lld", &x);
s[i] = s[i - 1] + x;
}
ll ans = 0;
_for(i, 1, n)
_for(p1, 0, n / 2 + 1)
_for(p2, 0, n / 3 + 1)
_for(p3, 0, n / 4 + 1)
_for(p4, 0, n / 5 + 1)
{
if(p1 * k[1] + p2 * k[2] + p3 * k[3] + p4 * k[4] > T) break;
ll res = dp[(i - 1 + 6) % 6][p1][p2][p3][p4];
if(p1 - 1 >= 0 && i - 2 >= -1) res = max(res, dp[(i - 2 + 6) % 6][p1 - 1][p2][p3][p4] + s[i] - s[i - 1]);
if(p2 - 1 >= 0 && i - 3 >= -1) res = max(res, dp[(i - 3 + 6) % 6][p1][p2 - 1][p3][p4] + s[i] - s[i - 2]);
if(p3 - 1 >= 0 && i - 4 >= -1) res = max(res, dp[(i - 4 + 6) % 6][p1][p2][p3 - 1][p4] + s[i] - s[i - 3]);
if(p4 - 1 >= 0 && i - 5 >= -1) res = max(res, dp[(i - 5 + 6) % 6][p1][p2][p3][p4 - 1] + s[i] - s[i - 4]);
dp[i % 6][p1][p2][p3][p4] = res;
if(i == n) ans = max(ans, dp[i % 6][p1][p2][p3][p4]);
}
printf("%lld\n", ans);
return 0;
}
L LCS-like Problem(dp)
这题当时我想到从t中处理一个ban[][]数组,然后再s中dp找一个最长的不会被ban的序列
我卡住点的在于,如果当前要选一个字符,那么显然要和之前选过的所有字符看会不会被ban,那么是很费时间的。
其实,只需要判断最近的那个是否被ban,如果最近的那个不会被ban,那么和前面所有的都不会被ban
因为考虑s和t匹配,如果s中i<j,那么在t中对应的位置一定是i>j 那么就有在t中的子序列恰好是s中反过来的。
那么新来一个字符,如果它不和最近的冲突,那么它在t中匹配一定在这个最近的之前,也就在所有的之前。
知道这一点后就很好处理了。
首先可能出现s中的字符t中没有匹配的情况,这种时候这些字符是一定选的,处理一下就好。
那么用dp[j]表示以字符j结尾的最长序列,那么每次对当前的s[i],枚举j,看能不能加上即可
#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
int dp[30], ban[30][30], vis[30], ans;
int main()
{
string s, t;
cin >> s >> t;
int len = t.size();
for(int i = len - 1; i >= 0; i--)
{
_for(j, 0, 25)
if(vis[j])
ban[t[i] - 'a'][j] = 1;
vis[t[i] - 'a'] = 1;
}
for(auto x: s)
{
if(!vis[x - 'a']) ans++;
else
{
int cur = 0;
_for(j, 0, 25)
if(!ban[j][x - 'a'])
cur = max(cur, dp[j] + 1);
dp[x - 'a'] = max(dp[x - 'a'], cur);
}
}
int mx = 0;
_for(i, 0, 25) mx = max(mx, dp[i]);
printf("%d\n", mx + ans);
return 0;
}
J Gachapon(构造)
从样例猜构造方法,可惜猜的是最低为1/2 其实把最低的换成n就对了
这个分数加减比较复杂,考虑容易约分的形式
也就是1/ 2 *2/3 * 3/4……
即(1-1/2)(1-1/3)……
所以可以考虑构造这种分数
设初始为A,然后1/m到1/n
然后一波激情运算,注意一些算式可以表示为一个变量,这样推起来方便,因为最终目的是为了写程序。
注意到变量只有m和A,于是可以解出m和A关系
最后枚举m算出A,看A是否满足条件即可
#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
typedef long long ll;
ll x, y;
ll gcd(ll a, ll b) { return !b ? a : gcd(b, a % b); }
int main()
{
scanf("%lld%lld", &x, &y);
ll sum = 0;
_for(i, 2, x - 1) sum += i;
_for(m, x - 1, 1e9)
{
ll n = m - x + 3;
ll t = sum + (n - 1) * x;
ll up = m * y - t;
ll down = m - t;
if(up < 0 && down < 0) up = -up, down = -down;
if(up * down <= 0 || up >= down) continue;
ll d = gcd(up, down);
up /= d;
down /= d;
if(down > 10000) continue;
printf("%lld %lld\n", up, down);
for(ll i = m; i >= n; i--) printf("1 %lld\n", i);
puts("1 1");
break;
}
return 0;
}
抽卡(概率)
入门题,只需要算一下不可能的概率,然后用1减去就行
#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
typedef long long ll;
const int mod = 1e9 + 7;
const int N = 1e5 + 10;
int a[N], b[N], n;
ll binpow(ll a, ll b)
{
ll res = 1;
for(; b; b >>= 1)
{
if(b & 1) res = res * a % mod;
a = a * a % mod;
}
return res;
}
ll inv(ll x) { return binpow(x, mod - 2); }
int main()
{
scanf("%d", &n);
_for(i, 1, n) scanf("%d", &a[i]);
_for(i, 1, n) scanf("%d", &b[i]);
ll ans = 1;
_for(i, 1, n)
ans = ans * (a[i] - b[i]) % mod * inv(a[i]) % mod;
printf("%lld\n", (1 - ans + mod) % mod);
return 0;
}
周二
一袋小球(概率dp)
讨论一下什么情况先手能赢即可。
注意边界条件
#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
const int N = 1e3 + 10;
double dp[N][N];
int A, B;
int main()
{
scanf("%d%d", &A, &B);
_for(i, 1, A) dp[i][0] = 1;
_for(a, 1, A)
_for(b, 1, B)
{
dp[a][b] = 1.0 * a / (a + b);
if(b >= 3) dp[a][b] += 1.0 * b / (a + b) * (b - 1) * (b - 2)
/ (a + b - 1) / (a + b - 2) * dp[a][b - 3];
if(a >= 1 && b >= 2)
dp[a][b] += 1.0 * b / (a + b) * (b - 1) / (a + b - 1) * a
/ (a + b - 2) * dp[a - 1][b - 2];
}
printf("%.10f\n", dp[A][B]);
return 0;
}
P2473 [SCOI2008] 奖励关(状压dp)
记住概率顺推,期望逆推。
首先n很小,马上想到状压dp
如果是正常的思路,那么就是对于当前S,枚举它的子集转移过来
但是这题要算期望,怎么处理呢?这题有一些条件限制,满足si才能买,这使得概率并不均匀,考虑起来很复杂
因此反过来处理,也就是逆推,就很好处理
在当前这个状态下,抽到哪一个东西的概率是均等的,所以就加上n种情况的结果,然后除以n即可。
做期望的时候,逆推的思考方式是很方便的。
此外,这题我开始在想最优策略是什么,其实意思就是dp取最大值,这样就是最优策略
#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
int p[20], sta[20], n, k;
double dp[110][1 << 15];
int main()
{
scanf("%d%d", &k, &n);
rep(i, 0, n)
{
scanf("%d", &p[i]);
int x;
while(scanf("%d", &x) && x)
sta[i] |= 1 << (x - 1);
}
for(int i = k; i >= 1; i--)
rep(S, 0, 1 << n)
{
rep(j, 0, n)
{
if((S & sta[j]) == sta[j]) dp[i][S] += max(dp[i + 1][S], dp[i + 1][S | 1 << j] + p[j]); //注意包含的写法
else dp[i][S] += dp[i + 1][S];
}
dp[i][S] /= n;
}
printf("%.6f\n", dp[1][0]); //有些题目固定说保留几位小数,要注意
return 0;
}
Accumulation Degree(换根dp+细节)
这题的细节搞了我好久
首先对于流量,按照题目的从根到各个叶子,流量会分叉比较难思考,所以我们反过来,看作从叶子到根的流量
用dp[i]表示以i为根的子树的最大流量
那么dp[u] = sigma min(w, dp[v])
特例是如果u是叶子的话,dp[u] = 0同时v是叶子的话,取w而不是min(w, dp[v])
所以儿子v的贡献是需要分类讨论的,而当换根的时候也会出现这种情况,v可能是儿子,u可能变成儿子,如果它们成儿子时是叶子的话,贡献就不一样。
所以干脆写一个函数min(w, v == 0 ? 1e9 : v) 来计算贡献
这样就非常清晰了,不用考虑哪些度数为1的情况
#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
const int N = 2e5 + 10;
vector<pair<int, int>> g[N];
int dp[N], f[N], n;
int val(int w, int v)
{
return min(w, v == 0 ? (int)1e9 : v);
}
void dfs(int u, int fa)
{
dp[u] = 0;
for(auto x: g[u])
{
int v = x.first, w = x.second;
if(v == fa) continue;
dfs(v, u);
dp[u] += val(w, dp[v]);
}
}
void dfs2(int u, int fa)
{
for(auto x: g[u])
{
int v = x.first, w = x.second;
if(v == fa) continue;
f[v] = dp[v] + val(w, f[u] - val(w, dp[v]));
dfs2(v, u);
}
}
int main()
{
int T; scanf("%d", &T);
while(T--)
{
scanf("%d", &n);
_for(i, 1, n) g[i].clear();
_for(i, 1, n - 1)
{
int u, v, w;
scanf("%d%d%d", &u, &v, &w);
g[u].push_back({v, w});
g[v].push_back({u, w});
}
dfs(1, 0);
f[1] = dp[1];
dfs2(1, 0);
int res = 0;
_for(i, 1, n) res = max(res, f[i]);
printf("%d\n", res);
}
return 0;
}
周三
[NOIP2017]宝藏(状压dp)
好题啊
n这么小肯定是状压或者搜索,要马上反应过来
这题的难点在于怎么处理这个生成树
发现价值的计算只与层数有关,所以我们并不关心具体的构造,只需要关心层数
那么用dp[S][i]表示当前生成树已选的点为S,有i层的最小花费
要转移的话,就枚举第i层选哪些点,这些点的花费全部算为乘上i
注意,这样虽然会把答案算偏大,但是一定包括了正确答案
那么转移方程就是dp[S][i] = min(dp[S0][i - 1] + cost(S0, S))
S0是S的子集,枚举状态,再枚举它的子集,时间复杂度是3^n,不是4^n
考虑这里的cost(S0,S)该怎么算,显然可以枚举多出来的点向S0中的点连边
这个东西可以预处理,不写在循环中而加快速度。
预处理是3 ^ n * n ^ 2 dp是3 ^ n * n 花费主要在预处理
#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
int n, m, g[20][20], f[1 << 12][1 << 12], dp[1 << 12][20];
int main()
{
memset(g, 0x3f, sizeof g);
scanf("%d%d", &n, &m);
while(m--)
{
int u, v, w;
scanf("%d%d%d", &u, &v, &w);
u--; v--;
g[u][v] = g[v][u] = min(g[u][v], w);
}
memset(f, 0x3f, sizeof f);
rep(S, 0, 1 << n)
for(int S0 = S; S0; S0 = (S0 - 1) & S)
{
int res = 0;
rep(j, 0, n)
if((S & (1 << j)) && !(S0 & (1 << j)))
{
int cur = 1e9;
rep(k, 0, n)
if(S0 & (1 << k))
cur = min(cur, g[j][k]);
if(cur == 1e9)
{
res = 1e9;
break;
}
else res += cur;
}
f[S0][S] = res;
}
memset(dp, 0x3f, sizeof dp);
rep(i, 0, n) dp[1 << i][0] = 0;
rep(i, 1, n - 1)
rep(S, 0, 1 << n)
for(int S0 = S; S0; S0 = (S0 - 1) & S)
if(f[S0][S] < 1e9)
dp[S][i] = min(dp[S][i], dp[S0][i - 1] + f[S0][S] * i);
int ans = 1e9;
_for(i, 0, n - 1) ans = min(ans, dp[(1 << n) - 1][i]);
printf("%d\n", ans);
return 0;
}
刷野(区间dp)
从数据范围可以看出是区间dp
但是这个区间dp有点技巧
在一个区间里面选一个野怪时,它的左右边是谁呢?
不一定,所以我们就把左边的杀完,右边的杀完,在杀当前这个,这样它的左边是l-1,右边是r+1
人为规定了这个顺序后,就可以计算了,而且这个规定也没有很特殊,可以包含所有情况
#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
const int N = 500 + 10;
int dp[N][N], a[N], b[N], n;
int main()
{
scanf("%d", &n);
_for(i, 1, n) scanf("%d", &a[i]);
_for(i, 1, n) scanf("%d", &b[i]);
memset(dp, 0x3f, sizeof dp);
_for(i, 1, n) dp[i][i] = a[i] + b[i - 1] + b[i + 1];
_for(len, 2, n)
_for(l, 1, n)
{
int r = l + len - 1;
if(r > n) break;
_for(k, l + 1, r - 1)
dp[l][r] = min(dp[l][r], dp[l][k - 1] + dp[k + 1][r] + a[k] + b[l - 1] + b[r + 1]);
dp[l][r] = min(dp[l][r], a[l] + dp[l + 1][r] + b[l - 1] + b[r + 1]);
dp[l][r] = min(dp[l][r], a[r] + dp[l][r - 1] + b[l - 1] + b[r + 1]);
}
printf("%d\n", dp[1][n]);
return 0;
}
[HAOI2011]PROBLEM A(dp)
首先转化一下,求最多右多少人说了真话,这样比较容易处理。
首先可以转化成排名区间,我一开始想的是,只要两个区间有交集就是冲突的,所以直接按照右端点排序贪心选取即可,然后就WA了
关键点在于一个特例,如果右多个相同的区间,且它们长度大于1,那么根据这道题,就是合法的!我就是这点没考虑到
考虑到这点后,就给区间附上了权值,且区间权值的最大值就是区间长度。
那么问题就变成了每个区间有权值,选不相交的区间使得权值最大
这个可以dp解决,dp[i]表示1~i的最优值,然后枚举右端点为i的区间转移即可
#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
const int N = 1e5 + 10;
map<pair<int, int>, int> mp;
vector<pair<int, int>> g[N];
int dp[N], n, ans;
int main()
{
scanf("%d", &n);
_for(i, 1, n)
{
int a, b;
scanf("%d%d", &a, &b);
if(a + b < n) mp[{b + 1, n - a}]++;
}
for(auto x: mp)
{
int l = x.first.first, r = x.first.second, v = x.second;
g[r].push_back({l, min(r - l + 1, v)});
}
_for(i, 1, n)
{
dp[i] = dp[i - 1];
for(auto x: g[i])
{
int j = x.first, v = x.second;
dp[i] = max(dp[i], v + dp[j - 1]);
}
}
printf("%d\n", n - dp[n]);
return 0;
}
Min酱要旅行(背包拓展)
逆向思考,不取i的,那就把一定取i的去掉
那么问题就转为如何求体积为j,一定取i的方案数
那么就是j-w[i]后,一定不取i的方案数
那么干脆就把g[j]表示为体积为j,一定不取i的方案数
有g[j] = f[j] - g[j - w[i]]
因此求出f之后,递推求出g数组即可
#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
const int N = 2500;
int f[N], g[N], w[N], n, m;
int main()
{
scanf("%d%d", &n, &m);
_for(i, 1, n) scanf("%d", &w[i]);
f[0] = 1;
_for(i, 1, n)
for(int j = m; j >= w[i]; j--)
f[j] = (f[j] + f[j - w[i]]) % 10;
_for(i, 1, n)
{
_for(j, 0, m) //注意体积从0开始枚举
{
g[j] = f[j];
if(j - w[i] >= 0) g[j] = (g[j] - g[j - w[i]] + 10) % 10;
}
_for(j, 1, m) printf("%d", g[j]);
puts("");
}
return 0;
}
周四
管道取珠(思维+dp)
这题的关键在于平方和
考虑其物理意义
即两个一模一样的装置,结果相同的方案数
比如一个装置中某个方案为ai种,另一个相同的装置此方案也是ai
那么相同的就是ai^2
那么就根据这个dp
dp[i][j][k][l]表示第一个装置上面出了i个,下面出了j个,第二个装置上面k个下面l个时相同的方案数
那么如果a[i] == a[k] dp[i][j][k][l] += dp[i - 1][j][k - 1][l]
这样是n的四次方的
但是发现l是多余的,因为i+j=k+l
所以可以优化掉一个维度
初始化为dp[0][0][0] = 1 转移时从0开始枚举,注意负数情况
#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
const int N = 500 + 10;
const int mod = 1024523;
int dp[N][N][N], n, m;
char a[N], b[N];
int main()
{
scanf("%d%d%s%s", &n, &m, a + 1, b + 1);
reverse(a + 1, a + n + 1);
reverse(b + 1, b + m + 1);
dp[0][0][0] = 1;
_for(i, 0, n)
_for(j, 0, m)
_for(k, 0, n)
{
int l = i + j - k;
if(i && k && a[i] == a[k]) dp[i][j][k] += dp[i - 1][j][k - 1];
if(i && l && a[i] == b[l]) dp[i][j][k] += dp[i - 1][j][k];
if(j && k && b[j] == a[k]) dp[i][j][k] += dp[i][j - 1][k - 1];
if(j && l && b[j] == b[l]) dp[i][j][k] += dp[i][j - 1][k];
dp[i][j][k] %= mod;
}
printf("%d\n", dp[n][m][n]);
return 0;
}
周五
打砖块(观察+dp)
这题的关键点在于,看成一列一列的放,同时下一列的高度要大于等于当前这一列减去一
发现无论怎么选都有这一个规律
根据这个dp即可,一列一列来
注意有一列什么都不放的情况
最后统计答案要统计最后一列,因为中间有一些不合法的情况
也可也从第n列从第1列,这样每个状态都是合法的
#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
const int N = 50 + 10;
const int M = 500 + 10;
int dp[N][N][M], a[N][N], sum[N][N], n, m;
int main()
{
scanf("%d%d", &n, &m);
_for(i, 1, n)
_for(j, 1, n - i + 1)
scanf("%d", &a[i][j]);
_for(j, 1, n)
_for(i, 1, n - j + 1)
sum[j][i] = sum[j][i - 1] + a[i][j];
memset(dp, -0x3f, sizeof dp);
_for(i, 0, n) dp[1][i][i] = sum[1][i];
_for(j, 1, n)
_for(i, 0, n - j + 1)
_for(l, max(i - 1, 0), n - j)
_for(k, 0, m - l)
dp[j + 1][l][k + l] = max(dp[j + 1][l][k + l], dp[j][i][k] + sum[j + 1][l]);
printf("%d\n", max(dp[n][0][m], dp[n][1][m]));
return 0;
}
周六
[HAOI2010]最长公共子序列(dp+方案数)
这题有很多点可以学习
用g[i][j]表示前i个和前j个的最长公共子序列的个数
可以先用dp转移完,然后再统计方案数
由哪些状态可以转移过来,就转移哪些状态的方案数
但是有一个特例,当dp[i][j] == dp[i - 1][j - 1]时,多的a[i]和b[j]并没有产生贡献
这时g[i - 1][j - 1]给了g[i][j - 1],再给g[i][j],同时g[i - 1][j - 1]给了g[i - 1][j] 再给g[i][j]
重复计算了,所以要减去g[i - 1][j - 1]
此外,此题限制空间,需要滚动数组。
滚动数组的时候要记得当前的数组是已经有值的,要处理一下,要么覆盖要么初始化为0,也就是清空。
g数组的初始化为长度为0的最长公共子序列的方案数为1。
#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
typedef long long ll;
const int N = 5000 + 10;
const int mod = 1e8;
char a[N], b[N];
int dp[2][N], g[2][N];
int main()
{
scanf("%s%s", a + 1, b + 1);
int lena = strlen(a + 1) - 1;
int lenb = strlen(b + 1) - 1;
g[1][0] = 1;
_for(i, 0, lenb) g[0][i] = 1;
_for(i, 1, lena)
{
int cur = i % 2, pre = (i - 1) % 2;
_for(j, 1, lenb)
{
dp[cur][j] = max(dp[pre][j], dp[cur][j - 1]);
if(a[i] == b[j]) dp[cur][j] = max(dp[cur][j], dp[pre][j - 1] + 1);
g[cur][j] = 0;
if(a[i] == b[j] && dp[pre][j - 1] + 1 == dp[cur][j]) g[cur][j] += g[pre][j - 1];
if(dp[cur][j] == dp[pre][j]) g[cur][j] += g[pre][j];
if(dp[cur][j] == dp[cur][j - 1]) g[cur][j] += g[cur][j - 1];
if(dp[cur][j] == dp[pre][j - 1]) g[cur][j] -= g[pre][j - 1];
g[cur][j] = (g[cur][j] + mod) % mod;
}
}
printf("%d\n%d\n", dp[lena % 2][lenb], g[lena % 2][lenb]);
return 0;
}
[AHOI2009]CHESS 中国象棋(dp求方案数)
数据比较小的时候可以用三进制状压做,和之前做一个八皇后方案数是类似的
但是题目给的是100,就无法状压了
考虑简化状态,实际上只需要考虑有多少列放了1个,多少放了0个,多少放了2个即可
用dp[i][j][k]表示前i行,有j列放了0个,k列放了1个
对于放了2个即m-j-k
那么枚举当前行怎么放即可
为了思考的方便,可以写成往后推的dp。
#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
typedef long long ll;
const int mod = 9999973;
const int N = 110;
ll dp[N][N][N];
int n, m;
int main()
{
scanf("%d%d", &n, &m);
dp[1][m][0] = 1;
dp[1][m - 1][1] = m;
dp[1][m - 2][2] = m * (m - 1) / 2;
_for(i, 1, n - 1)
_for(j, 0, m) // 0
_for(k, 0, m) // 1
{
int l = m - j - k; // 2
if(l < 0) break;
//一个都不放
dp[i + 1][j][k] = (dp[i + 1][j][k] + dp[i][j][k]) % mod;
//放一个
//放在0列
if(j > 0) dp[i + 1][j - 1][k + 1] = (dp[i + 1][j - 1][k + 1] + dp[i][j][k] * j % mod) % mod;
//放在1列
if(k > 0) dp[i + 1][j][k - 1] = (dp[i + 1][j][k - 1] + dp[i][j][k] * k % mod) % mod;
//放两个
//1个0列 1个0列
if(j > 1) dp[i + 1][j - 2][k + 2] = (dp[i + 1][j - 2][k + 2] + dp[i][j][k] * (j * (j - 1) / 2) % mod) % mod;
//1个1列 1个0列
if(j > 0 && k > 0) dp[i + 1][j - 1][k] = (dp[i + 1][j - 1][k] + dp[i][j][k] * j * k % mod) % mod;
//1个1列 1个1列
if(k > 1) dp[i + 1][j][k - 2] = (dp[i + 1][j][k - 2] + dp[i][j][k] * (k * (k - 1) / 2) % mod) % mod;
}
ll ans = 0;
_for(j, 0, m)
_for(k, 0, m)
{
int l = m - j - k;
if(l < 0) break;
ans = (ans + dp[n][j][k]) % mod;
}
printf("%lld\n", ans);
return 0;
}
[SCOI2009]粉刷匠(dp)
读完题就知道是一个分组背包,关键是怎么求一行。
我一开始卡住在于觉得涂的时候只是涂一部分,其实可以发现把全部涂满的方案一定是最优方案之一,因为不涂白不涂,有了这一点就容易dp了
f[i][j]表示用j次把前i块涂满的最多正确的值,每次枚举最后一次涂多少就可以。
#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
const int N = 60;
const int M = 2500 + 10;
int f[N][N], a[N], s[N], dp[M], n, m, T;
vector<pair<int, int>> ve[N];
void deal(int x)
{
memset(f, 0, sizeof f);
_for(i, 1, m)
_for(j, 1, i)
_for(k, 1, i)
f[i][j] = max(f[i][j], f[k - 1][j - 1] + max(s[i] - s[k - 1], (i - k + 1) - (s[i] - s[k - 1])));
_for(j, 1, m) ve[x].push_back({j, f[m][j]});
}
int main()
{
scanf("%d%d%d", &n, &m, &T);
_for(i, 1, n)
{
_for(j, 1, m) scanf("%1d", &a[j]), s[j] = s[j - 1] + a[j];
deal(i);
}
_for(i, 1, n)
for(int j = T; j >= 0; j--)
for(auto x: ve[i])
{
int w = x.first, v = x.second;
if(j >= w) dp[j] = max(dp[j], dp[j - w] + v);
}
printf("%d\n", dp[T]);
return 0;
}
区间价值(dp)
用f[i]表示区间长度为i的答案
观察怎么转移 首先最后一个会消失,所以要求一个后缀不同数字的个数,可以O(n)求
然后发现,对于一个区间增加,要贡献1就要使得区间中没有这个数
转化一下,也就是这个数和前一个相同的差要大于等于i
那么我们可以求每个数与前面的数的差等于i的值,然后求一个后缀和使得大于等于i
#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
typedef long long ll;
const int N = 1e6 + 10;
int a[N], vis[N], dif[N], pre[N], k[N], cnt, n;
ll f[N];
int main()
{
scanf("%d", &n);
_for(i, 1, n) scanf("%d", &a[i]);
for(int i = n; i >= 1; i--)
{
if(!vis[a[i]])
{
vis[a[i]] = 1;
cnt++;
}
dif[n - i + 1] = cnt;
}
_for(i, 1, n)
{
k[i - pre[a[i]]]++;
pre[a[i]] = i;
}
for(int i = n - 1; i >= 1; i--) k[i] += k[i + 1];
f[1] = n;
_for(i, 2, n) f[i] = f[i - 1] + k[i] - dif[i - 1];
int q; scanf("%d", &q);
_for(i, 1, q)
{
int x; scanf("%d", &x);
printf("%lld\n", f[x]);
}
return 0;
}