CF41D Pawn
题意:
给定矩阵,每个格子都有一个权值。每次可以向右上或者左上走。要求权值和最大且为 k + 1 k+1 k+1的倍数。询问最大权值和,起始位置和移动方案。
解析:
如果没有倍数限制的话,简单的二维 d p dp dp。 d p i , j dp_{i,j} dpi,j表示到达 ( i , j ) (i,j) (i,j)能获得的最大权值。
因为整除限制,再加一维: d p i , j , k dp_{i,j,k} dpi,j,k表示到达 ( i , j ) (i,j) (i,j)时,是否存在方案使权值和为 k k k。状态转移的时候记录以下路径。
时间复杂度: O ( n m ( n k ) ) O(nm(nk)) O(nm(nk))
代码:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 1e2+10;
int f[maxn][maxn][maxn*10];
char g[maxn][maxn][maxn*10];
int a[maxn][maxn];
int n, m, kk;
void print(int x, int y, int k){
if(x == n){
cout << y << endl;
return;
}
if(g[x][y][k] == 'R'){
print(x+1, y-1, k-a[x][y]);
cout << "R";
}
else if(g[x][y][k] == 'L'){
print(x+1, y+1, k-a[x][y]);
cout << "L";
}
}
int main(){
cin >> n >> m >> kk;
for(int i = 1; i <= n; i++)
for(int j = 1; j <= m; j++)
scanf("%1d", &a[i][j]);
//memset(f, 0, sizeof(f));
for(int i = 1; i <= m; i++)
f[n][i][a[n][i]] = 1;
for(int i = n-1; i >= 1; i--){
for(int j = 1; j <= m; j++){
for(int k = 1000; k >= a[i][j]; k--){
if(j >= 2){
if(f[i+1][j-1][k-a[i][j]]){
f[i][j][k] = 1;
g[i][j][k] = 'R';
}
}
if(j <= m-1){
if(f[i+1][j+1][k-a[i][j]]){
f[i][j][k] = 1;
g[i][j][k] = 'L';
}
}
}
}
}
for(int i = 1000; i >= 0; i--){
if(i%(kk+1) != 0)
continue;
for(int j = 1; j <= m; j++){
if(f[1][j][i]){
cout << i << endl;
print(1, j, i);
return 0;
}
}
}
cout << -1 << endl;
return 0;
}
CF893E Counting Arrays
题意:
每次询问 ( x , y ) (x,y) (x,y),长度为 y y y且乘积为 x x x的序列的个数,序列中的元素可以为负数
解析:
首先考虑正负的问题,前 y − 1 y-1 y−1个位置元素符号可以为任意,由第 y y y个元素保证乘积为正数,所以对答案的贡献为 2 y − 1 2^{y-1} 2y−1。
由算数基本定理可得: x = p 1 a 1 p 2 a 2 . . . p m a m x = p_1^{a_1}p_2^{a_2}...p_m^{a_m} x=p1a1p2a2...pmam对于每个 p i p_i pi,在序列中一定要"出现" a i a_i ai次,才能保证乘积为 x x x。将 a i a_i ai个 p i p_i pi分到 y y y个位置,可以存在位置没有分配到。由隔板法可知,对答案的贡献为 C ( y + a i − 1 , y − 1 ) C(y+a_i-1, y-1) C(y+ai−1,y−1).
代码:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 1e6+100;
const int mod = 1e9+7;
ll fac[maxn], facv[maxn];
ll C(ll a, ll b){
ll res = fac[a]%mod*facv[b]%mod*facv[a-b]%mod;
return res%mod;
}
ll qpow(ll a, ll b){
ll res = 1;
while(b){
if(b&1)
res = res*a%mod;
a = a*a%mod;
b = b >> 1;
}
return res%mod;
}
ll inv(ll x){
return qpow(x, mod-2)%mod;
}
void init(int x){
fac[0] = facv[0] = 1;
for(int i = 1; i <= x; i++){
fac[i] = fac[i-1]*i%mod;
facv[i] = inv(fac[i])%mod;
}
}
void solve(){
ll x, y;
cin >> x >> y;
ll ans = 1;
for(ll i = 2; i*i <= x; i++){
if(x%i == 0){
ll cnt = 0;
while(x%i == 0){
x /= i;
cnt++;
}
ans = ans*C(y+cnt-1, cnt)%mod;
}
}
if(x > 1)
ans = ans*C(y, 1)%mod;
ans = ans*qpow(2, y-1)%mod;
cout << ((ans%mod)+mod)%mod << endl;
}
int main(){
ios::sync_with_stdio(0);
cin.tie(0);cout.tie(0);
init(1000050);
int T;
cin >> T;
while(T--)
solve();
return 0;
}
CF855C Helga Hufflepuff’s Cup
题意:
给定一棵树,可以染 m m m 种颜色,定义一个特殊颜色 k k k,要求保证整棵树上特殊颜色的个数不超过 x x x 个。同时,如果一个结点是特殊颜色,那么它的相邻结点的颜色编号必须全部小于 k k k。求方案数。
解析:
树上dp。
每个点的染色分为三种:小于
k
k
k,等于
k
k
k,大于
k
k
k
令 d p i , j , s dp_{i,j,s} dpi,j,s 表示以 i i i 为根的子树内有 j j j 个特殊颜色,且 i i i 的染色为 s s s 的方案数。
然后就不会了。以下是看了 一扶苏一 \color{red}{一扶苏一} 一扶苏一 的题解才会的,直接看大佬的题解更明白一点。
当一个结点多个儿子时,要考虑每增加一个点的产生的贡献。
设 g i , j , s g_{i,j,s} gi,j,s为 u u u的子树中,考虑前 i i i个儿子,一共选了 j j j个 k k k,结点 u u u的状态为 s s s
当 u u u选小于 k k k时,儿子可以选 0 / 1 / 2 0/1/2 0/1/2 三种情况
当 u u u选等于 k k k时,儿子只能选 0 0 0 一种情况
当 u u u选大于 k k k时,儿子可以选 0 / 2 0/2 0/2 两种情况
g g g可以滚掉一维
代码:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 1e5+10;
const int maxm = 2e5+10;
const int INF = 0x3f3f3f3f;
const int mod = 1e9+7;
int head[maxn], tot;
struct edge{
int to, nxt;
}e[maxm<<1];
void add(int a, int b){
e[++tot].nxt = head[a];
e[tot].to = b;
head[a] = tot;
}
ll f[maxn][15][3];
ll g[maxn][2][15][3];
int n, m, k, x;
void init(int u){
g[u][0][0][0] = k-1;
g[u][0][0][2] = m-k;
g[u][0][1][1] = 1;
}
void dfs(int u, int p){
init(u);
int pre = 0;
for(int i = head[u]; i; i = e[i].nxt){
int v = e[i].to;
if(v == p)
continue;
dfs(v, u);
pre ^= 1;
memset(g[u][pre], 0, sizeof(g[u][pre]));
for(int j = 0; j <= x; j++){
for(int h = 0; h <= j; h++){
g[u][pre][j][0] = (g[u][pre][j][0]+g[u][pre^1][j-h][0]*(f[v][h][0]+f[v][h][1]+f[v][h][2])) % mod;
g[u][pre][j][1] = (g[u][pre][j][1]+g[u][pre^1][j-h][1]*f[v][h][0]) % mod;
g[u][pre][j][2] = (g[u][pre][j][2]+g[u][pre^1][j-h][2]*(f[v][h][0]+f[v][h][2])) % mod;
}
}
}
for(int i = 0; i <= x; i++)
for(int j = 0; j < 3; j++)
f[u][i][j] = g[u][pre][i][j];
}
signed main(){
cin >> n >> m;
for(int i = 1; i < n; i++){
int a, b;
cin >> a >> b;
add(a, b);
}
cin >> k >> x;
dfs(1, 0);
ll ans = 0;
for(int i = 0; i <= x; i++)
for(int j = 0; j < 3; j++)
ans = (ans+f[1][i][j]) % mod;
cout << ans << endl;
return 0;
}
CF960F Pathwalks
题意:
给定有向图。求最长的路径,使得这条路径的编号和权值都严格单调递增,编号是指输入的顺序。路径的长度是经过边的数量。
分析:
因为要按照编号递增,所以按照输入顺序依次处理每条边。
设 d p u , x dp_{u,x} dpu,x是以 u u u为终点,最后一条路径权值为 x x x的最长路径的长度。后边有一条边 ( u , v , w ) (u,v,w) (u,v,w),(表示 u u u到 v v v,权值为 w w w)
则 d p v , w = m a x { d p u , x } + 1 , x < w dp_{v,w} = max\{dp_{u,x}\}+1,x<w dpv,w=max{dpu,x}+1,x<w
每次转移的时间是 O ( w ) O(w) O(w),会超时。需要单点修改和区间查询,可以用线段树来优化转移。
每个点建一棵线段树,维护权值对应的最长路径。
代码:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 1e5+10;
const int maxm = 2e7+10;
struct edge{
int x, y, w;
}e[maxn];
struct node{
int ls, rs;
int maxx;
}t[maxm];
int tot = 0;
void newnode(int &k){
k = ++tot;
t[k].ls = t[k].rs = 0;
t[k].maxx = 0;
}
void pushup(int k){
t[k].maxx = max(t[t[k].ls].maxx, t[t[k].rs].maxx);
}
int query(int k, int l, int r, int x, int y){
if(k == 0)
return 0;
if(x <= l && y >= r)
return t[k].maxx;
int mid = (l+r) >> 1;
int res = 0;
if(x <= mid)
res = max(res, query(t[k].ls, l, mid, x, y));
if(y > mid)
res = max(res, query(t[k].rs, mid+1, r, x, y));
return res;
}
void modify(int &k, int l, int r, int pos, int v){
if(!k)
newnode(k);
if(l == r){
t[k].maxx = max(t[k].maxx, v);
return;
}
int mid = (l+r) >> 1;
if(pos <= mid)
modify(t[k].ls, l, mid, pos, v);
else
modify(t[k].rs, mid+1, r, pos, v);
pushup(k);
}
int n, m;
int rt[maxn];
int ans = 0;
int main(){
cin >> n >> m;
for(int i = 1; i <= m; i++){
int u, v, w;
cin >> u >> v >> w;
int tmp = query(rt[u], 0, 100000, 0, w-1)+1;
modify(rt[v], 0, 100000, w, tmp);
ans = max(ans, tmp);
}
cout << ans << endl;
return 0;
}
CF847E Packmen
题意:
一维直线上有若干包裹和人,人移动一单位距离需要一秒,询问得到所有包裹的最短时间
解析:
答案具有单调性,考虑二分答案。
- 人的路径不会相交
- 每个人最多掉头一次
考虑贪心。最左边的包裹只能由最左边的人得到,这个人可以先往左得到这个包裹,然后一直向右走直到无法走;也可以向右走一定的距离,然后向左得到最左边的包裹
代码:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 2e5+10;
const int INF = 0x3f3f3f3f;
int cntp, cnts, ans;
int n;
int p[maxn], s[maxn];
char ss[maxn];
int check(int x){
int nowp = 1, nows = 1;
for(int i = 1; i <= n; i++){
if(ss[i] == '.')
continue;
if(ss[i] == 'P'){
if(p[nowp] > s[nows]){ //左边还有
int dis = p[nowp] - s[nows];
if(dis > x)
return 0;
int rdis = x-2*dis;
while(s[nows]-p[nowp] <= max(0, rdis))
nows++;//先左再右
while(2*(s[nows]-p[nowp])+dis <= x)
nows++;//先右再左
}
else{
while(s[nows] - p[nowp] <= x)
nows++;
}
nowp++;
}
if(nowp == cntp+1)
break;
if(nows == cnts+1)
break;
}
return nows == cnts+1;
}
int main(){
cin >> n;
cin >> ss+1;
for(int i = 1; i <= n; i++){
if(ss[i] == 'P')
p[++cntp] = i;
else if(ss[i] == '*')
s[++cnts] = i;
}
s[cnts+1] = INF;
int l = 1, r = 2*n;
while(l <= r){
int mid = (l+r) >> 1;
if(check(mid)){
ans = mid;
r = mid-1;
}
else
l = mid+1;
}
cout << ans << endl;
return 0;
}