ICPC2020 南京站
ICPC2020 南京站
H Harmonious Rectangle
简要题意
3染色,求满足存在轴向端点颜色相同矩形的染色方案个数。\(1 \leq n,m \leq 2000\)
题解
不妨设\(n\leq m\)。对于\(n=1\),构不成矩形无解。根据抽屉原理,当\(n \geq 2\)时,一行中前两个点染色情况只有\(9\)种,如果\(m > 9\),所有情况一定都满足条件。所以我们只需要考虑\(m \leq 9\)的情况,通过搜索计算不存在的方案数,事实上状态数很少,大多数情况在搜索中剪枝减去了。我的写法是暴力打表,但看其他人代码有的把情况数限制到\(7\),不知道为什么。
#include <bits/stdc++.h>
#define LL long long
#define pb push_back
#define mp make_pair
#define pii pair<int,int>
#define fi first
#define se second
#define lc nd<<1
#define rc nd<<1|1
#define lowbit(x) (x&(-x))
#define pLL pair<LL,LL>
using namespace std;
const int mn=1e5+6,mod=1e9+7;
int n,m,c[15][15];
LL cnt;
int ans[15][15]={
{},
{0,3,9,27,81,243,729,2187,6561,19683},
{0,9,66,390,1800,6120,13680,15120,0,0},
{0,27,390,3198,13176,27000,13680,15120,0,0},
{0,81,1800,13176,24336,4320,0,0,0,0},
{0,243,6120,27000,4320,4320,0,0,0,0},
{0,729,13680,13680,0,0,0,0,0,0},
{0,2187,15120,15120,0,0,0,0,0,0},
{0,6561,0,0,0,0,0,0,0,0},
{0,19683,0,0,0,0,0,0,0,0}
};
int read()
{
int ans=0;char c=getchar();
while(!isdigit(c)) c=getchar();
while(isdigit(c)) ans=ans*10+c-'0',c=getchar();
return ans;
}
LL qpow(LL a,LL b) {
LL ans=1;
while(b) {
if(b&1) ans=ans*a%mod;
a=a*a%mod;
b>>=1;
}
return ans;
}
void dfs(int x,int y)
{
if(y>m) x++,y=1;
if(x>n) {
cnt++;
return ;
}
for(int a=1;a<=3;++a) {
c[x][y]=a;
int flag=1;
for(int i=1;i<x;++i) for(int j=1;j<y;++j) {
if(c[i][y]==c[x][y]&&c[i][j]==c[x][j]) flag=0;
if(c[x][j]==c[x][y]&&c[i][j]==c[i][y]) flag=0;
}
if(flag) dfs(x,y+1);
c[x][y]=0;
}
}
int main()
{
int tests=1;scanf("%d",&tests);
while(tests--) {
n=read(),m=read();
if(n>m) swap(n,m);
if(n==1) {puts("0");continue;}
if(m>=9) {
printf("%lld\n",qpow(3,n*m));
}
else {
if(ans[n][m]==-1) {
cnt=0;
dfs(1,1);
ans[n][m]=cnt%mod;
}
printf("%lld\n",(qpow(3,n*m)+mod-ans[n][m])%mod);
}
}
return 0;
}
M Monster Hunter
简要题意
给一棵树,每个点的权值是自身权值加上所有未删的儿子的权值,求删\(0-n\)个点分别对应的最小权值和。
题解
树形dp,考虑把贡献拆开,每个点的贡献会在自身删不删被考虑,在父亲删不删被考虑。三维的状态,\(f_{i,j,0/1}\)表示在\(i\)的子树内删\(j\)个点,\(0\)表示自身不删,\(1\)表示自身要删。
转移就是类似背包的转移,根据当前情况和儿子情况算和。
\[f_{i,j+k,0}=min(f_{i,j,0}+f_{t,k,0}+hp_t,f_{i,j,0}+f_{t,k,1}) \\
f_{i,j+k,1}=min(f_{i,j,1}+f_{t,k,0},f_{i,j,1}+f_{t,k,1}) \\
\]
这样转移是\(O(n^2)\)的,因为合并子树的过程只会做一次,两个点只会在lca合并一次。
#include <bits/stdc++.h>
#define LL long long
#define pb push_back
#define mp make_pair
#define pii pair<int,int>
#define fi first
#define se second
#define lc nd<<1
#define rc nd<<1|1
#define lowbit(x) (x&(-x))
#define pLL pair<LL,LL>
using namespace std;
const int mn=3005;
const LL inf=1e18;
vector<int> to[mn];
int hp[mn],n,siz[mn];
LL f[mn][mn][2],sum,tmp[mn];
void dfs(int x)
{
for(int i=0;i<=n+1;++i) f[x][i][0]=f[x][i][1]=inf;
siz[x]=1;
f[x][0][0]=hp[x];
f[x][1][1]=0;
for(auto t:to[x]) {
dfs(t);
for(int i=0;i<=siz[x]+siz[t];++i) tmp[i]=inf;
for(int i=0;i<=siz[x];++i) for(int j=0;j<=siz[t];++j) {
tmp[i+j]=min(tmp[i+j],f[x][i][0]+f[t][j][1]);
tmp[i+j]=min(tmp[i+j],f[x][i][0]+f[t][j][0]+hp[t]);
}
for(int i=0;i<=siz[x]+siz[t];++i) f[x][i][0]=tmp[i];
for(int i=0;i<=siz[x]+siz[t];++i) tmp[i]=inf;
for(int i=0;i<=siz[x];++i) for(int j=0;j<=siz[t];++j) {
tmp[i+j]=min(tmp[i+j],f[x][i][1]+f[t][j][1]);
tmp[i+j]=min(tmp[i+j],f[x][i][1]+f[t][j][0]);
}
for(int i=0;i<=siz[x]+siz[t];++i) f[x][i][1]=tmp[i];
siz[x]+=siz[t];
}
}
int main()
{
int tests=1;scanf("%d",&tests);
while(tests--) {
sum=0;
scanf("%d",&n);
for(int i=1;i<=n;++i) to[i].clear();
int fa;
for(int i=2;i<=n;++i) {
scanf("%d",&fa);
to[fa].pb(i);
}
for(int i=1;i<=n;++i) {
scanf("%d",&hp[i]);
}
dfs(1);
for(int i=0;i<=n;++i) printf("%lld%c",min(f[1][i][0],f[1][i][1]),(i==n?'\n':' '));
}
return 0;
}

浙公网安备 33010602011771号