诡异数字
https://ac.nowcoder.com/acm/problem/20669
思路:dp[pos, pre, cnt]表示当前位置是pos,上一位出现的数是pre, 这个数出现cnt次的数量。
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N = 25, mod = 20020219;
ll t ,l, r, n, numlim[N], num[N], dp[N][10][1010];
ll dfs(int pos, bool limit, bool lead, int pre, int cnt){
if(!pos) return 1;
if(!limit && !lead && ~dp[pos][pre][cnt]) return dp[pos][pre][cnt];
int up = limit ? num[pos] : 9;
ll ans = 0;
for(int i = 0; i <= up; ++ i){
if(lead && i == 0) ans = (ans + dfs(pos - 1, limit && i == up, true, -1, 0)) % mod;
else{
if(i == pre){
if(cnt == numlim[i]) continue;
else ans = (ans + dfs(pos - 1, limit && i == up, false, i, cnt + 1)) % mod;
}else ans = (ans + dfs(pos - 1, limit && i == up, false, i, 1)) % mod;
}
}
if(!limit && !lead) dp[pos][pre][cnt] = ans;
return ans;
}
ll solve(ll x){
memset(dp, -1, sizeof dp);
int pos = 0;
while(x){
num[++ pos] = x % 10;
x /= 10;
}
return dfs(pos, true, true, -1, 0);
}
int main(){
cin >> t;
while(t --){
cin >> l >> r >> n;
memset(numlim, 0x3f, sizeof numlim);
for(int i = 1; i <= n; ++ i){
ll x, cnt;
cin >> x >> cnt;
numlim[x] = min(numlim[x], cnt);
}
cout << (solve(r) - solve(l - 1) + mod) % mod << endl;
}
return 0;
}7的意志
https://ac.nowcoder.com/acm/problem/20665
再次证明牛客的星是xjb标的qwq 这是个锤子5星题
思路:用remain存这个数%7的值,sum存这个数每一位相加的和%7的余数。
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N = 25;
ll l, r, dp[N][10][10];
int num[N];
ll dfs(int pos, bool limit, int sum, int remain){
if(!pos) return (sum % 7 == 0 && remain % 7 == 0);
if(!limit && ~dp[pos][sum][remain]) return dp[pos][sum][remain];
ll ans = 0;
int up = limit ? num[pos] : 9;
for(int i = 0; i <= up; ++ i) ans += dfs(pos - 1, limit && i == up, (sum + i) % 7, (remain * 10 + i) % 7);
if(!limit) dp[pos][sum][remain] = ans;
return ans;
}
ll solve(ll x){
int pos = 0;
while(x){
num[++ pos] = x % 10;
x /= 10;
}
return dfs(pos, true, 0, 0);
}
int main(){
memset(dp, -1, sizeof dp);
while(cin >> l >> r){
if(!l && !r) break;
cout << solve(r) - solve(l - 1) << endl;
}
return 0;
}树上子链
https://ac.nowcoder.com/acm/problem/202475
思路:类似树的最长路径求法, dp表示的是选择当前点的情况下能得到的最大单链的值(不能是折线形的, 否则没法推了)用ans实时更新答案(这时可能是折线形的)
tips:可能点权全是负的,所以ans初始化的时候不可以是0.
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N = 100010, M = 2 * N, inf = 0x3f3f3f3f;
int idx, ne[M], e[M], h[N];
int n;
ll dp[N],ans = -1e12,w[N];
void add(int a, int b){ e[idx] = b, ne[idx] = h[a], h[a] = idx ++;}
void dfs(int u, int fa){
ll d1 = 0, d2 = 0;
for(int i = h[u]; ~i; i = ne[i]){
int j = e[i];
if(j == fa) continue ;
dfs(j, u);
if(dp[j] >= d1) d2 = d1, d1 = dp[j];
else if(dp[j] > d2) d2 = dp[j];
}
d1 += w[u];
dp[u] = d1;
ans = max(d1 + d2, ans);
}
int main(){
cin >> n;
memset(h, -1, sizeof h);
for(int i = 1; i <= n; ++ i) scanf("%lld", &w[i]);
for(int i = 1; i <= n - 1; ++ i){
int a, b;
scanf("%d%d",&a,&b);
add(a, b), add(b, a);
}
dfs(1, 1);
//for(int i = 1; i <= n; ++ i) printf("%d ", dp[i]);
cout << ans;
return 0;
}
/*
5
-1 -1 -1 -1 -1
1 2
2 3
3 4
4 5
-1
*/吉吉王国
https://ac.nowcoder.com/acm/problem/210473
思路:求最大边权最小, 而且最大边权有限制, 想到二分+树形dp check.
tips:这题只是要求最大边权最小, 没有要求总的最小, 要和Rinne Loves Edges 区分开。 详见代码注释的两组样例。
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N = 1010, M = 2 * N, inf = 0x3f3f3f3f;
int idx, ne[M], e[M], w[M], h[N];
int n,m,s;
ll dp[N];
void add(int a, int b, int c){ e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx ++;}
void dfs(int u, int fa, int limit){
bool f = false;
for(int i = h[u]; ~i; i = ne[i]){
int j = e[i];
if(j == fa) continue ;
dfs(j, u, limit);
if(w[i] > limit) dp[u] += dp[j];
else dp[u] += min(dp[j], 1ll * w[i]);
f = true;
}
if(!f) dp[u] = inf;
}
int main(){
cin >> n >> m;
memset(h, -1, sizeof h);
for(int i = 1; i <= n - 1; ++ i){
int a, b, c;
scanf("%d%d%d",&a,&b,&c);
add(a, b, c), add(b, a, c);
}
int l = 0, r = 1010;
while(l < r){
int mid = (l + r) >> 1;
memset(dp, 0, sizeof dp);
dfs(1, 1, mid);
if(dp[1] > m) l = mid + 1;
else r = mid;
//printf("mid = %d l = %d r = %d dp = %d\n", mid,l, r, dp[1]);
}
if(r == 1010) puts("-1");
else cout << r;
return 0;
}
/*
4 11
1 2 10
2 3 5
2 4 6
6
4 10
1 2 10
2 3 5
2 4 6
10
*/Cell Phone Network
https://ac.nowcoder.com/acm/problem/24953
AcWing 皇宫看守
注意被儿子覆盖的情况比较复杂,需要求出所有儿子的min(dp0, dp1)的和之后减掉当前选择的这个儿子的min,再加上在这个儿子放守卫的情况, 但是发现这个min的和就是已经求出的dp2
/*
dp状态转移方程:
0表示自己覆盖 dp(u,0) += min(dp[j,0],dp[j,1],dp[j,2])
1表示儿子覆盖(自己不放) 找出一个儿子,剩下的+min(dp[j,0],dp[j,1]) +
2表示父亲覆盖(自己不放) dp[u,2] += min(dp[j,0], dp[j,1])
*/
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N = 10010, M = 2 * N;
int idx,ne[M],e[M],h[N],dp[N][3],n;
void add(int a, int b){ e[idx] = b, ne[idx] = h[a], h[a] = idx ++;}
void dfs(int u, int fa){
dp[u][0] = 1;
for(int i = h[u]; ~i; i = ne[i]){
int j = e[i];
if(j == fa) continue ;
dfs(j, u);
dp[u][0] += min(dp[j][0], min(dp[j][1], dp[j][2]));
dp[u][2] += min(dp[j][0], dp[j][1]);
}
dp[u][1] = 1e9;
for(int i = h[u]; ~i; i = ne[i]){
int j = e[i];
if(j == fa) continue ;
dp[u][1] = min(dp[u][1], dp[u][2] - min(dp[j][0], dp[j][1]) + dp[j][0]);
}
}
int main(){
cin >> n;
memset(h, -1, sizeof h);
for(int i = 1; i <= n - 1; ++ i){
int a, b;
cin >> a >> b;
add(a, b), add(b, a);
}
dfs(1, 1);
cout << min(dp[1][0], dp[1][1]);
return 0;
}
Accumulation Degree
https://ac.nowcoder.com/acm/problem/51180
思路: 换根DP.
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N = 200010, M = 2 * N;
int idx, ne[M], e[M], h[N], in[N], w[M];
int n,m,t;
ll dp[N], dp2[N];
void add(int a, int b, int c){ e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx ++;}
void dfs1(int u, int fa){
for(int i = h[u]; ~i; i = ne[i]){
int j = e[i];
if(j == fa) continue ;
dfs1(j, u);
if(in[j] == 1) dp[u] += w[i];
else dp[u] += min(dp[j], 1ll * w[i]);
}
}
void dfs2(int u, int fa){
for(int i = h[u]; ~i; i = ne[i]){
int j = e[i];
if(j == fa) continue ;
if(in[u] == 1) dp2[j] = w[i] + dp[j];
else dp2[j] = dp[j] + min(1ll * w[i], dp2[u] - min(dp[j], 1ll * w[i]));
dfs2(j, u);
}
}
void solve(){
idx = 0;
memset(h, -1, sizeof h);
memset(in, 0, sizeof in);
memset(dp, 0, sizeof dp);
cin >> n;
for(int i = 1; i <= n - 1; ++ i){
int a, b, c;
scanf("%d%d%d", &a,&b,&c);
add(a, b, c), add(b, a, c), in[a] ++, in[b] ++;
}
dfs1(1, 1);
dp2[1] = dp[1];
dfs2(1, 1);
//for(int i = 1; i <= n; ++ i) printf("%d ", dp2[i]);
ll ans = 0;
for(int i = 1; i <= n; ++ i) ans = max(ans, dp2[i]);
cout << ans << endl;
}
int main(){
cin >> t;
while(t --) solve();
return 0;
}
Tree
https://ac.nowcoder.com/acm/problem/19782
思路: 换根DP. 从下往上dfs的时候, 父亲的dp值 *= 儿子的dp + 1【这个1可以理解为这个子树的空集。如果所有子树都取空集,那就相当于之选当前这一个点】
从上往下dfs的时候有一个坑点, dp + 1有可能等于1e9 + 7, 这时没有逆元, 没法用快速幂+费马小求逆元, 所以我们可以暴力算得到该点的dp2. 注意dfs1就是暴力算以某个点为根的情况, 所以这里直接调用dfs1即可。
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N = 1e6 + 10, M = 2 * N, mod = 1e9 + 7;
int idx, ne[M], e[M], h[N];
int n;
ll dp[N],dp2[N];
ll ksm(ll a, ll b){
ll res = 1;
while(b){
if(b & 1) res = res * a % mod;
a = a * a % mod;
b >>= 1;
}
return res;
}
void add(int a, int b){ e[idx] = b, ne[idx] = h[a], h[a] = idx ++;}
void dfs(int u, int fa){
dp[u] = 1;
for(int i = h[u]; ~i; i = ne[i]){
int j = e[i];
if(j == fa) continue ;
dfs(j, u);
dp[u] = (dp[u] * (dp[j] + 1)) % mod;
}
}
void dfs2(int u, int fa){
for(int i = h[u]; ~i; i = ne[i]){
int j = e[i];
if(j == fa) continue ;
if((dp[j] + 1) % mod == 0){
dfs(j, j);
dp2[j] = dp[j];
}
dp2[j] = (dp[j] * (dp2[u] * ksm((dp[j] + 1), mod - 2) % mod + 1)) % mod;
dfs2(j, u);
}
}
int main(){
cin >> n;
memset(h, -1, sizeof h);
for(int i = 1; i <= n - 1; ++ i){
int a, b;
scanf("%d%d",&a,&b);
add(a, b), add(b, a);
}
dfs(1, 1);
dp2[1] = dp[1];
dfs2(1, 1);
for(int i = 1; i <= n; ++ i) printf("%d\n", dp2[i]);
return 0;
} 
京公网安备 11010502036488号