2017四川省赛D.Dynamic Graph(类floyd的动态规划,或者bitset)

题目大意:

给你一个有向无环图,一开始所有点都是白色的点,现在每次操作给一个点v,将他的颜色翻转(白色点变为黑色点,黑色点变为白色点),你需要在每次操作之后,回答这个图中有多少对(u,v)节点,u,v节点为白色,且u到v有一条全部为白色节点的路径。

 

思路:

如果每次询问求一次任意两点之间是否相通,复杂度为O(n^4),无法接受,不过有一种类似暴力的方法,用bitset维护图,这样可以加速修改,贴上代码:

bitset维护:

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
int n,m,q;
const int N=3e2+10;
vector<int>G[N];
int col[N],du[N],d[N],tx[N];
int bfs()
{
    bitset<310>bit[310];
 
    queue<int>que;
    for(int i=1;i<=n;++i){
        d[i]=du[i],tx[i]=0;
        bit[i][i]=1;
        if(d[i]==0)que.push(i);
    }
 
    while(que.size())
    {
        int u=que.front();que.pop();
 
 
        for(int v:G[u])
        {
            if(col[u]==0&&col[v]==0) {
                bit[v]|=bit[u];
            }
            d[v]--;
            if(d[v]==0) que.push(v);
        }
    }
    int ans=0;
    for(int i=1;i<=n;++i)
    {
        ans+=bit[i].count()-1;
    }
    return ans;
}
int main()
{
    while(~scanf("%d%d%d",&n,&m,&q))
    {
        for(int i=1;i<=n;++i) col[i]=0,G[i].clear(),du[i]=0;
        for(int i=1;i<=m;++i)
        {
            int u,v;scanf("%d%d",&u,&v);
            G[u].push_back(v);
            du[v]++;
        }
 
        while(q--)
        {
            int u;
            scanf("%d",&u);
            col[u]=1-col[u];
            printf("%d\n",bfs());
        }
    }
}

 

但是这里用类似floyd的做法更加好一点,f(i,j)表示i到j的路径总数为多少,g(i,j)为原图的邻接矩阵,那么每次进来一个点v,我们可以枚举路径的起点i和终点j,分两种情况:

1.如果是白点变黑点,那么我们只需要减去通过这个点的路径总数就可以,并且更新一下g数组;

2.如果是黑点变白点,那么我们更新以v为起点,i为终点的新增路径数,再更新以v为终点,i为起点的新增路径数,最后再枚举起点i和终点j,更新答案就好了。这样的复杂度为O(n^3)

#include <bits/stdc++.h>
#define LL long long
using namespace std;
const int N = 307;
​
int f[N][N];//点u到点v的路径条数
int c[N][N];//原图,判断u到v是否有边
bool vis[N];
char s[N];
int main(){
    //freopen("in.txt", "r", stdin);
    int n, m, q;
    while (scanf("%d%d%d", &n, &m, &q) != EOF){
        memset(f, 0, sizeof(f)); memset(c, 0, sizeof(c));
        for (int i = 1; i <= m; i++){
            int u, v;
            scanf("%d%d", &u, &v);
            f[u][v] = c[u][v] = 1;
        }
        //求路径条数
        for (int k = 1; k <= n; k++){
            for (int i = 1; i < k; i++){
                for (int j = k + 1; j <= n; j++){
                    f[i][j] = f[i][j] + f[i][k] * f[k][j];
                }
            }
        }
        memset(vis, 0, sizeof(vis));
        for (int i = 1; i <= q; i++){
            int u; //u = rand() % n + 1;
            scanf("%d", &u);
            if (!vis[u]){//由白变黑,只需要判断减少了多少条边
                vis[u] = true;
                for (int j = 1; j < u; j++){
                    for (int k = u + 1; k <= n; k++){
                        f[j][k] = f[j][k] - f[j][u] * f[u][k];
                    }
                }
                for (int j = 1; j <= n; j++) f[j][u] = f[u][j] = 0;
            } else {
                //由黑变白,判断增加了多少条路径
                vis[u] = false;
                //更新以v为起点,i为终点的新增路径数
                for (int j = u + 1; j <= n; j++) if (!vis[j]){
                    for (int k = u + 1; k < j; k++) if (c[u][k]){
                        f[u][j] += f[k][j];
                    }
                    f[u][j] += c[u][j];
                }
                //更新以v为终点,i为起点的新增路径数
                for (int j = 1; j < u; j++) if (!vis[j]){
                    for (int k = j + 1; k < u; k++) if (c[k][u]){
                        f[j][u] += f[j][k];
                    }
                    f[j][u] += c[j][u];
                }
                //枚举起点i和终点j
                for (int j = 1; j < u; j++){
                    for (int k = u + 1; k <= n; k++){
                        f[j][k] = f[j][k] + f[j][u] * f[u][k];
                    }
                }
            }
            int ans = 0;
            for (int j = 1; j <= n; j++){
                for (int k = j + 1; k <= n; k++){
                    if (f[j][k]) ans++;
                }
            }
            printf("%d\n", ans);
        }
    }
    return 0;
}
​

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值