0%

190512 Spring Training 4

比赛链接

题目 A B C D E F
通过 ×
补题

A - 皮格和回文

一个 \(n\times m\) 的每个格子中有一个字母,求左上角以最短路走向右下角中,路径上的字符串是回文串的方案数。

题解

\(x+y\) 作为转移的一维,从回文串的中间向串的两侧 DP 转移即可。时间复杂度 \(\mathcal{O}(n^3)\)

代码

点击显示 / 隐藏

1
代码写得太丑,并且没有过

B - 打印文章

一个非负数列 \(a_i\),可以将它划分为多段,每一段 \([l,r]\) 的代价为 \((\sum_{i=l}^r a_i)^2 + M\),求最小化代价。

题解

斜率优化模板题,具体可以参考之前写的一篇总结

代码

点击显示 / 隐藏

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;

typedef long long ll;
const int N=500000+5;
const double INF=1e100;
ll f[N],s[N],g[N];
int n,m;

double Slope(int i,int j){
if(s[i]==s[j])return INF;
else return double(g[i]-g[j])/(s[i]-s[j]);
}

int BSearch(int L,int R,double x,int q[]){
while(L<R){
int M=(L+R)/2;
if(Slope(q[M],q[M+1])<x)L=M+1;
else R=M;
}
return L;
}

ll DP(){
static int q[N];
int p=0; q[++p]=0;

for(int i=1;i<=n;i++){
int j=q[BSearch(1,p,2*s[i],q)];
f[i]=f[j]+(s[i]-s[j])*(s[i]-s[j])+m;
g[i]=f[i]+s[i]*s[i];
while(p>=2&&Slope(q[p-1],q[p])>=Slope(q[p],i))p--;
q[++p]=i;
}

return f[n];
}

int main(){
while(cin>>n>>m){
for(int i=1;i<=n;i++){
cin>>s[i];
s[i]+=s[i-1];
}
cout<<DP()<<endl;
}

return 0;
}

C - 玉米地

一块 \(n\times m\) 的土地中有一些地方能种,一些地方不能种。两块相邻的土地不能都种,求合法的方案数。

题解

水水的状压 DP。

代码

点击显示 / 隐藏

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
#include<iostream>
#include<cstring>
using namespace std;

const int N=12+2,M=(1<<12)+5,MOD=100000000;
int f[N][M];
int msk[N];
int n,m;

int DP(){
f[0][0]=1;
for(int i=1;i<=n;i++)
for(int j=0;j<(1<<m);j++){
if(j&(j<<1))continue;
if((j|msk[i-1])!=msk[i-1])continue;
for(int k=0;k<(1<<m);k++){
if(k&(k<<1))continue;
if((k|msk[i])!=msk[i])continue;
if(j&k)continue;
f[i][k]=(f[i][k]+f[i-1][j])%MOD;
}
}

int ans=0;
for(int j=0;j<(1<<m);j++)
ans=(ans+f[n][j])%MOD;
return ans;
}

int main(){
cin>>n>>m;
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++){
int c; cin>>c;
msk[i]=(msk[i]<<1)|(c==1);
}
cout<<DP();

return 0;
}

D - 只是一些排列

很神秘的题,并没有搞懂。

题解

代码

点击显示 / 隐藏

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
```
{%endfold%}







## E - 另一个最小化问题



### 题解

很神秘的题,听说可以用带权二分写,有空补上。

### 代码

{%fold%}
```c++

F - 树上染色

给一个 \(n(\leq 2000)\) 个节点的树的 \(k\) 个节点染黑,\(n-k\) 个节点染白。一种染色方案的价值为所有同色点对之间的边权和,求最大价值。

题解

只要用 \(f(u,i)\) 记录一下在点 \(u\) 的子树中染色 \(i\) 个黑点的最大价值,然后使用树上背包增加每次多选择的点对答案的贡献即可。但是枚举点、枚举当前子树的染色点数、枚举待合并子树的染色点数,看起来是 \(\mathcal{O}(n^3)\) 的啊?

考虑每次枚举的两棵子树中的点,它们不重不漏地枚举了以当前节点 \(u\) 为 LCA 的两个子树中的点,因此这样的时间复杂度是 \(\mathcal{O}(n^2)\) 的。

得到一个结论:枚举 bound 是两棵子树的 size 的操作,其时间复杂度一般都是 \(\mathcal{O}(n^2)\) 的。

代码

点击显示 / 隐藏

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
#include<iostream>
#include<vector>
using namespace std;

typedef long long ll;
const int N=2000+5;
int n,nWhite,nBlack;
struct Adj{int v; ll w;};
vector<Adj> a[N];

ll f[N][N]; int siz[N];

void DFS(int u,int pa){
siz[u]=1;
for(int i=0;i<a[u].size();i++){
int v=a[u][i].v; ll w=a[u][i].w;
if(v==pa)continue;
DFS(v,u);

siz[u]+=siz[v];
for(int i=min(siz[u],nWhite);i>=max(0,siz[u]-nBlack);i--)
for(int j=max(0,i-siz[u]+siz[v]);j<=min(siz[v],i);j++){
ll tmp=w*(1LL*j*(nWhite-j)+1LL*(siz[v]-j)*(nBlack-siz[v]+j));
f[u][i]=max(f[u][i],f[u][i-j]+f[v][j]+tmp);
}
}
}

int main(){
cin>>n>>nWhite;
nBlack=n-nWhite;
for(int i=1;i<n;i++){
int u,v; ll w;
cin>>u>>v>>w;
a[u].push_back((Adj){v,w});
a[v].push_back((Adj){u,w});
}

DFS(1,0);
cout<<f[1][nWhite];

return 0;
}