HDU 6321 Dynamic Graph Matching & 6331 Walking Plan (DP)

hdu6321 对一个图进行加边或删边操作(n<=10),在每次操作后求图的匹配数为k的方案数,其中k=1,2,...n/2。

由于点的数目很少,考虑状压dp,dp[k][i]表示第k次操作后在i状态下点集中最大匹配的方案数,显然对于x个点的状态匹配数最多是x/2个。

考虑加边u-v的操作,如果对于一个状态pre,状态中没有u和v两个点,那么就可以从pre状态转移到now状态,其中now是pre以及u和v的并。删除边的时候同理。可以看到这里转移的时候只需要前一次操作的结果,于是可以使用滚动数组减小空间的使用。

#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<cmath>
#include<queue>
#include<vector>
#include<map>
#include<iostream>
#include<algorithm>
#define maxn 10050
#define INF 0x3f3f3f3f
#define eps 1e-8
using namespace std;
typedef long long ll;
const ll mod = 1000000007;

int t,n,m,u,v,cnt;
char s[5];
ll dp[2][maxn],ans[maxn];
int num[maxn],sta[maxn];
int val[15];

void init()
{
    cnt = 0;
    val[0] = 1;
    for(int i = 1;i <= 10;i++)val[i] = val[i-1] * 2;
    for(int i = 0;i < 1024;i++)
    {
        num[i] = num[i>>1] + (i&1);
        if(num[i]%2==0)
            sta[cnt++] = i;
    }
}

int main()
{
    scanf("%d",&t);
    init();
    while(t--)
    {
        scanf("%d%d",&n,&m);
        int now = 0;
        memset(dp,0,sizeof(dp));
        dp[0][0] = 1;
        for(int j = 0;j < m;j++)
        {
            scanf("%s%d%d",s,&u,&v);
            u--,v--;
            int tmp = (val[u] | val[v]);
            memset(ans,0,sizeof(ans));
            for(int i = 0;i < cnt && sta[i] < (1<<n);i++)
            {
                int cur = sta[i];
                dp[now ^ 1][cur] = dp[now][cur];
                if((cur & tmp) == tmp)
                {
                    if(s[0] == '+')
                        dp[now ^ 1][cur] = (dp[now ^ 1][cur] + dp[now][cur ^ tmp]) % mod;
                    else
                        dp[now ^ 1][cur] = (dp[now ^ 1][cur] - dp[now][cur ^ tmp] + mod) % mod;
                }
                ans[num[cur]/2] = (ans[num[cur]/2] + dp[now ^ 1][cur])%mod;
            }
            for(int i = 1;i <= n/2;i++)
                printf(i == n/2 ? "%lld\n" : "%lld ",ans[i]);
            now ^= 1;
        }
    }
    return 0;
}

hdu6331 给出一个n个点和m条边的图,要求q次查询,每次查询从s到t经过不少于k条边(可重复)的最短距离。n<=50,m,k<=10000,q<=100000。

 显然要先预处理点之间的距离,直接处理的复杂度是O(n^3*m),是无法接受的,考虑一个分块的dp。

dp[i][j][k]表示从i到j恰好经过k条边的最短距离

dp1[i][j][k]表示从i到j至少经过k条边的最短距离

dp2[i][j][k]表示从i到j恰好经过k*100条边的最短距离 ,k<=100

dp[i][j][k]用原图就可以处理出来,而dp1[i][j][k]=min(dp[i][t][k]+dis[t][j]),其中dis[][]是原图处理出的传递闭包。

以及dp2[i][j][k]=min(dp2[i][t][k-1]+dp[k][j][100])。

最后查询时枚举中间点e,ans=min(dp2[i][e][k/100]+dp1[e][t][k%100])。

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define maxn 51
#define maxm 102
#define INF 0x3f3f3f3f
#define eps 1e-8
using namespace std;
typedef long long ll;

int kase,s,e,t,n,m,q,a,b,x;
int dis[maxn][maxn];
int maze[maxn][maxn];
int dp[maxn][maxn][maxm];
int dp1[maxn][maxn][maxm];
int dp2[maxn][maxn][maxm];
//0 k个 1 至少k 2 k*100个
int Min(int a,int b)
{
    if(a < 0)return b;
    else if(b < 0)return a;
    return a<b?a:b;
}

void solve_dp()
{
    memset(dp,-1,sizeof(dp));
    memset(dp1,-1,sizeof(dp1));
    memset(dp2,-1,sizeof(dp2));
    for(int i = 1;i <= n;i++)
        dp[i][i][0] = dp2[i][i][0] = 0;
    for(int i = 1;i <= n;i++)
    {
        for(int j = 1;j <= n;j++)
            dis[i][j] = maze[i][j];
        dis[i][i] = 0;
    }
    for(int k = 1;k <= n;k++)
    {
        for(int i = 1;i <= n;i++)
        {
            for(int j = 1;j <= n;j++)
            {
                if(dis[i][k] != -1 && dis[k][j] != -1)
                    dis[i][j]=Min(dis[i][j] , dis[i][k] + dis[k][j]);
            }
        }
    }
    for(int p = 1;p <= 100;p++)
    {
        for(int k = 1;k <= n;k++)
        {
            for(int i = 1;i <= n;i++)
            {
                for(int j = 1;j <= n;j++)
                {
                    if(dp[i][k][p - 1] != -1 && maze[k][j] != -1)
                        dp[i][j][p] = Min(dp[i][j][p] , dp[i][k][p - 1] + maze[k][j]);
                }
            }
        }
    }
    for(int p = 0;p <= 100;p++)
    {
        for(int k = 1;k <= n;k++)
        {
            for(int i = 1;i <= n;i++)
            {
                for(int j = 1;j <= n;j++)
                {
                    if(dp[i][k][p] != -1 && dis[k][j] != -1)
                        dp1[i][j][p] = Min(dp1[i][j][p] , dp[i][k][p] + dis[k][j]);
                }
            }
        }
    }
    for(int p = 1;p <= 100;p++)
    {
        for(int k = 1;k <= n;k++)
        {
            for(int i = 1;i <= n;i++)
            {
                for(int j = 1;j <= n;j++)
                {
                    if(dp2[i][k][p - 1] != -1 && dp[k][j][100] != -1)
                        dp2[i][j][p] = Min(dp2[i][j][p] , dp2[i][k][p - 1] + dp[k][j][100]);
                }
            }
        }
    }
}
//0 k个 1 至少k 2 k*100个
int main()
{
    scanf("%d",&kase);
    while(kase--)
    {
        scanf("%d%d",&n,&m);
        memset(maze,-1,sizeof(maze));
        for(int i = 0;i < m;i++)
        {
            scanf("%d%d%d",&a,&b,&x);
            maze[a][b] = Min(maze[a][b] , x);
        }
        solve_dp();
        scanf("%d",&q);
        for(int i = 0;i < q;i++)
        {
            scanf("%d%d%d",&s,&e,&t);
            int t1 = t/100;
            int t2 = t%100;
            int ans = -1;
            for(int k = 1;k <= n;k++)
            {
                if(dp2[s][k][t1] != -1 && dp1[k][e][t2] != -1)
                    ans = Min(ans , dp2[s][k][t1] + dp1[k][e][t2]);
            }
            printf("%d\n",ans);
        }
    }
    return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值