T1:这一道题一看就是树形dp,只不过方程有点复杂。
设f[x][0/1][0/1/2]表示以x为根的子树已经合法了,目前x所在的联通块中有0个0/超过0个0,0个1/1个1/超过1个1。转移推一下就好了。注意转移需要一个辅助dp。
代码如下:
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#define ll long long
#define MAXN 300010
#define MAXM 600010
using namespace std;
struct map
{
ll x;
ll y;
ll l;
};
map way[MAXM];
ll first[MAXN],next[MAXM],a[MAXN],bz[MAXN],fa[MAXN],f[MAXN][2][3],g[MAXN],dp[MAXN][2][3],n,m,T,s;
ll v,y,rd[MAXN];
int work(ll x,ll y,ll i,ll j){if(dp[s-1][x][y]+v<dp[s][i][j])dp[s][i][j]=dp[s-1][x][y]+v;}
int dfs(ll z)
{
ll i;
bz[z]=1;
for(i=first[z];i>=1&&i<=m;i=next[i])
if(bz[way[i].y]==0){fa[way[i].y]=z;rd[z]++;dfs(way[i].y);}
if(rd[z]==0)
{
if(a[z]==0)f[z][1][0]=0;
if(a[z]==1)f[z][0][1]=0;
if(a[z]==2)f[z][0][0]=0;
for(i=0;i<=1;i++)
for(s=0;s<=2;s++)
if(f[z][i][s]<g[z]&&(!(i==1&&s==2)))g[z]=f[z][i][s];
return 0;
}
s=0;
for(i=first[z];i>=1&&i<=m;i=next[i])
if(fa[way[i].y]==z)
{
s++;y=way[i].y;
memset(dp[s],0x7f,sizeof(dp[s]));
v=f[y][0][0];
work(0,0,0,0);
work(0,1,0,1);
work(0,2,0,2);
work(1,0,1,0);
work(1,1,1,1);
work(1,2,1,2);
v=f[y][0][1];
work(0,0,0,1);
work(0,1,0,2);
work(0,2,0,2);
work(1,0,1,1);
work(1,1,1,2);
work(1,2,1,2);
v=f[y][0][2];
work(0,0,0,2);
work(0,1,0,2);
work(0,2,0,2);
work(1,0,1,2);
work(1,1,1,2);
work(1,2,1,2);
v=f[y][1][0];
work(0,0,1,0);
work(0,1,1,1);
work(0,2,1,2);
work(1,0,1,0);
work(1,1,1,1);
work(1,2,1,2);
v=f[y][1][1];
work(0,0,1,1);
work(0,1,1,2);
work(0,2,1,2);
work(1,0,1,1);
work(1,1,1,2);
work(1,2,1,2);
v=f[y][1][2];
work(0,0,1,2);
work(0,1,1,2);
work(0,2,1,2);
work(1,0,1,2);
work(1,1,1,2);
work(1,2,1,2);
v=g[y]+way[i].l;
work(0,0,0,0);
work(0,1,0,1);
work(0,2,0,2);
work(1,0,1,0);
work(1,1,1,1);
work(1,2,1,2);
}
if(a[z]==2)f[z][0][0]=dp[s][0][0];
if(a[z]==1)f[z][0][1]=dp[s][0][0];
else if(a[z]==2)f[z][0][1]=dp[s][0][1];
if(a[z]==1)f[z][0][2]=min(dp[s][0][1],dp[s][0][2]);
else if(a[z]==2)f[z][0][2]=dp[s][0][2];
if(a[z]==0)f[z][1][0]=min(dp[s][0][0],dp[s][1][0]);
else if(a[z]==2)f[z][1][0]=dp[s][1][0];
if(a[z]==0)f[z][1][1]=min(dp[s][0][1],dp[s][1][1]);
else if(a[z]==1)f[z][1][1]=dp[s][1][0];
else f[z][1][1]=dp[s][1][1];
if(a[z]==0)f[z][1][2]=min(dp[s][0][2],dp[s][1][2]);
else if(a[z]==1)f[z][1][2]=min(dp[s][1][1],dp[s][1][2]);
else f[z][1][2]=dp[s][1][2];
for(i=0;i<=1;i++)
for(s=0;s<=2;s++)
if(f[z][i][s]<g[z]&&(!(i==1&&s==2)))g[z]=f[z][i][s];
}
ll read()
{
ll s=0;
char c=getchar();
while(c<'0'||c>'9')c=getchar();
while('0'<=c&&c<='9')s=s*10+c-'0',c=getchar();
return s;
}
int main()
{
ll i,j;
T=read();
while(T>=1)
{
n=read();
for(i=1;i<=n;i++)a[i]=read();
m=n-1;
for(i=1;i<=m;i++)
{
way[i].x=read();way[i].y=read();way[i].l=read();
way[i+m].x=way[i].y;way[i+m].y=way[i].x;way[i+m].l=way[i].l;
}
memset(first,0,sizeof(first));
m*=2;for(i=m;i>=1;i--)next[i]=first[way[i].x],first[way[i].x]=i;
memset(f,0x7f,sizeof(f));
memset(g,0x7f,sizeof(g));
memset(bz,0,sizeof(bz));
dfs(1);
printf("%lld\n",g[1]);
T--;
}
}
T2:首先求出1到n的所有的可能的最短路径,删去那些不可能出现在最短路径中边。而我们下载的任务就是要删去剩下图中的一些边使1和n不连通。
删去每一条边的代价就是这一条边两端的点的最小值。而这题很明显就是一个网络流模型。
最小代价我们已经算出来了,关键是怎样判断方案是否唯一。
首先我们要判断最小割是否唯一。这个怎么做呢?首先我们做一遍网络流,然后分别从1和n开始dfs,每次只能走还剩有流量的边。这样我们就把所有点分成了S、T以及不属于前两个的共三个集合。那么很显然的就是如果一条边一端在S集合,而另一端在T集合,那么这条边就是必删的(如果没有删除这一条边的话就还可以增广)。到这里我们就可以求出所有必删的边。记sum为这些边流量的和,如果sum=flow的话那么最小割唯一,否则最小割不唯一。
最后如果最小割唯一的话我们还要判断一下每一条必删的边两端的点权是否一样,如果存在一条必删边两端的点权一样的话,那么还是有多种方案的。
T3:显然可持久化线段树就可以了。只不过我们要同时从2k个点往下求和。
总结:这一题因为数组算小了所以20分。
以后算数组空间的时候,要注意:
1、明确有关变量(n、m、k等等)
2、若遇到log或者根号,那么就尽量开大一点。(比如可以开log的最大值+1位)