[JSOI2010]连通数

这个题目有毒。

我同学跟我说这个是强连通分量水题,然后我就打 tarjan t a r j a n ,结果 WAWAWA W A , W A , W A

思考许久没想出来哪里错了。

于是仔细思考,可以用BFS做,于是我很愉快的打了一个 BFS B F S ,AC啦!

原来 一道 省选/NOI- 的JS省选紫题就这么水过了

愉快的打了个 普及/提高-

回家仔细思考哪里错了。

发现自己有不少写错了

1)建边的时候 把 S[j]==1 S [ j ] == ‘ 1 ′ 写成了 0 ‘ 0 ′

2)强连通分量 没用 ins 还有 还有 把 tarjan(to) t a r j a n ( t o ) 写成了 tarjan(i) t a r j a n ( i )

3)new_E 把 所有的 e[i] e [ i ] 写成了 G[i] G [ i ] (看来够昏头的,机房效率不高啊)

4)solve 里 把所有的 G[x] G [ x ] 写成了 e[x] e [ x ] (正好写反了!!!)

5)没开long long

好了,不说自己的经历了,讲正解怎么做的

首先,BFS真的很好想,对,就是直接对于每个点遍历其路径即可,然后+n即可(i和i肯定是连通的么)。


// Author : harry
// Language : C++ (GNU C++ 11)
// Upload : luogu
// Time : 2018.9.13 
// Problem : 连通数 【bfs版】 
// Tell Myself : Think Twice Code Once
// All rights reserved

#include <map>
#include <set>
#include <cmath>
#include <ctime>
#include <queue>
#include <stack>
#include <vector>
#include <cstdio>
#include <cctype>
#include <string>
#include <cstring>
#include <cassert>
#include <climits>
#include <cstdlib>
#include <iostream>
#include <algorithm>
#include <functional>
using namespace std ;

#define rep(i,a,b) for (int (i)=(a);(i)<=(b);(i)++)
#define Rep(i,a,b) for (int (i)=(a)-1;(i)<(b);(i)++)
#define REP(i,a,b) for (int (i)=(a);(i)>=(b);(i)--)
#define reg(i,x) for (int (i)=head[x];(i);i=e[i].next)
#define clear(a) memset(a,0,sizeof(a))

#define ull unsigned long long
#define ll long long
#define ls ((x)<<1)
#define rs ((x)<<1|1)
#define mp make_pair
#define pb push_back
#define fi first
#define se second
#define endl '\n'
#define Pii pair<int,int>

const int N = 2010 ;
const int iinf = INT_MAX/2 ;
const ll linf = LONG_MAX/2 ;
const int MOD = 1e9+7 ;

inline int read(){
    int X=0,w=0; char ch=0;
    while(!isdigit(ch)) {w|=ch=='-';ch=getchar();}
    while(isdigit(ch)) X=(X<<3)+(X<<1)+(ch^48),ch=getchar();
    return w?-X:X;
}

void write(int x){
     if(x<0) putchar('-'),x=-x;
     if(x>9) write(x/10);
     putchar(x%10+'0');
}

int vis[N] ;
vector<int> e[N] ;
ll ans=0;
int n ;

void bfs(int rt){
    queue <int> q ;
    memset(vis,0,sizeof(vis)) ;
    q.push(rt) ;
    vis[rt]=1 ;
    while(!q.empty()){
        int x=q.front();q.pop() ;
        for (int i=0;i<e[x].size();i++)
        if (!vis[e[x][i]]){
            ans++ ;
            vis[e[x][i]]=1 ;
            q.push(e[x][i]) ;
        }
    }
}

string S ;

int main(){
    scanf("%d",&n) ;
    for (int i=1;i<=n;i++){
        cin>>S ;
        for (int j=0;j<n;j++) if (S[j]=='1') e[i].pb(j+1) ;
    }

    for (int i=1;i<=n;i++) bfs(i) ;
    printf("%lld\n",ans+n) ;
}

强连通的做法就要稍微难想一点。

嗯,首先这个题目给了我们一个定义:连通数:指途中可达点对的个数。

其实首先这个定义我就并没有十分看懂,

其实这个东西的意思非常简单,就是针对每一个点,

我们计算这个点所能够到达的点的数量之和,

(算上自身)然后将所有点的这个数量加起来就是连通数了。

可以通过缩点优化

缩完点之后,我们重新构图。

记录 sum[i] s u m [ i ] 表示i(缩完点后的新点)所表示的点包含旧图中点的个数

时间复杂度 O(n3) O ( n 3 )

f[i] f [ i ] 表示 i i 能到达的点集 s (用bitset优化)

构完图之后,把入度为0的点加入队列。

之后像类似拓扑排序那样当入度为0就加入队列

然后对于每一次 即将更新的 用这个更新 f[G[x][i]]|=f[x] f [ G [ x ] [ i ] ] | = f [ x ]

然后统计一下答案就AC了

回顾之后感觉还是挺水的


// Author : harry
// Language : C++ (GNU C++ 11)
// Upload : luogu 
// Time : 2018.9.13
// Problem : 连通数 【强连通版】 
// Tell Myself : Think Twice Code Once
// All rights reserved

#include <map>
#include <set>
#include <cmath>
#include <ctime>
#include <queue>
#include <stack>
#include <vector>
#include <bitset>
#include <cstdio>
#include <cctype>
#include <string>
#include <cstring>
#include <cassert>
#include <climits>
#include <cstdlib>
#include <iostream>
#include <algorithm>
#include <functional>
using namespace std ;

#define rep(i,a,b) for (int (i)=(a);(i)<=(b);(i)++)
#define Rep(i,a,b) for (int (i)=(a)-1;(i)<(b);(i)++)
#define REP(i,a,b) for (int (i)=(a);(i)>=(b);(i)--)
#define reg(i,x) for (int (i)=head[x];(i);i=e[i].next)
#define clear(a) memset(a,0,sizeof(a))

#define ull unsigned long long
#define ll long long
#define ls ((x)<<1)
#define rs ((x)<<1|1)
#define mp make_pair
#define pb push_back
#define fi first
#define se second
#define endl '\n'
#define Pii pair<int,int>

const int N = 2010 ;
const int iinf = INT_MAX/2 ;
const ll linf = LONG_MAX/2 ;
const int MOD = 1e9+7 ;

inline int read(){
    int X=0,w=0; char ch=0;
    while(!isdigit(ch)) {w|=ch=='-';ch=getchar();}
    while(isdigit(ch)) X=(X<<3)+(X<<1)+(ch^48),ch=getchar();
    return w?-X:X;
}

void write(int x){
     if(x<0) putchar('-'),x=-x;
     if(x>9) write(x/10);
     putchar(x%10+'0');
}

vector<int> e[N],G[N] ;

int low[N],dfn[N],ins[N],col[N],a[N],ind[N] ;
stack <int> s ;

bitset <N> f[N] ;
int n,sum,t;
ll ans ;

void tarjan(int rt){
    dfn[rt]=low[rt]=++t ;
    ins[rt]=1;s.push(rt) ;

    for (int i=0;i<e[rt].size();i++){
        int to=e[rt][i] ;
        if (!dfn[to]){
            tarjan(to) ;
            low[rt]=min(low[rt],low[to]) ;
        }
        else if (ins[to]) low[rt]=min(low[rt],dfn[to]) ;
    }

    if (dfn[rt]==low[rt]){ //缩点 
        col[rt]=++sum ;
        ins[rt]=-1 ;
        a[sum]=1 ;
        while(s.top()!=rt){
            col[s.top()]=sum ;
            ins[s.top()]=-1 ;
            a[sum]++ ;
            s.pop() ;
        }
        s.pop() ;
    }
}

void new_E() { //重新构图 
    for (int i=1;i<=n;i++)
    for (int j=0;j<e[i].size();j++)
    if (col[i]!=col[e[i][j]]){
        G[col[i]].pb(col[e[i][j]]) ;
        ind[col[e[i][j]]]++ ;
    }
}

void solve(){
    queue <int> q ;
    while (!q.empty()) q.pop() ;
    for (int i=1;i<=sum;i++) f[i][i]=1 ;
    for (int i=1;i<=sum;i++) if (!ind[i]) q.push(i) ; 
    while(!q.empty()){
        int x=q.front();q.pop() ;
        for (int i=0;i<G[x].size();i++){
            f[G[x][i]]|=f[x] ;
            ind[G[x][i]]-- ;
            if (!ind[G[x][i]]) q.push(G[x][i]) ;
        }
    }
}

string S ;

int main(){
    scanf("%d",&n) ;
    for (int i=1;i<=n;i++){
        cin>>S ;
        for (int j=0;j<n;j++) if (S[j]=='1') e[i].pb(j+1) ;
    }

    for (int i=1;i<=n;i++) if (!ins[i]) tarjan(i) ;

    new_E() ;
    solve() ;

    for (int i=1;i<=sum;i++)
    for (int j=1;j<=sum;j++)
    if (f[i][j]) ans+=a[i]*a[j] ;
    printf("%lld\n",ans) ;
}

受f[i]的启发,感觉floyd也能做这题

也是bitset

如果 f[j][i] f [ j ] [ i ] f[j]|=f[i] f [ j ] | = f [ i ]

然后统计一下也可以AC


// Author : harry
// Language : C++ (GNU C++ 11)
// Upload : luogu 
// Time : 2018.9.13
// Problem : [JSOI2010]连通数 【floyd 版】 
// Tell Myself : Think Twice Code Once
// All rights reserved

#include <map>
#include <set>
#include <cmath>
#include <ctime>
#include <queue>
#include <stack>
#include <vector>
#include <bitset>
#include <cstdio>
#include <cctype>
#include <string>
#include <cstring>
#include <cassert>
#include <climits>
#include <cstdlib>
#include <iostream>
#include <algorithm>
#include <functional>
using namespace std ;

#define rep(i,a,b) for (int (i)=(a);(i)<=(b);(i)++)
#define Rep(i,a,b) for (int (i)=(a)-1;(i)<(b);(i)++)
#define REP(i,a,b) for (int (i)=(a);(i)>=(b);(i)--)
#define reg(i,x) for (int (i)=head[x];(i);i=e[i].next)
#define clear(a) memset(a,0,sizeof(a))

#define ull unsigned long long
#define ll long long
#define ls ((x)<<1)
#define rs ((x)<<1|1)
#define mp make_pair
#define pb push_back
#define fi first
#define se second
#define endl '\n'
#define Pii pair<int,int>

const int N = 2010 ;
const int iinf = INT_MAX/2 ;
const ll linf = LONG_MAX/2 ;
const int MOD = 1e9+7 ;

inline int read(){
    int X=0,w=0; char ch=0;
    while(!isdigit(ch)) {w|=ch=='-';ch=getchar();}
    while(isdigit(ch)) X=(X<<3)+(X<<1)+(ch^48),ch=getchar();
    return w?-X:X;
}

void write(int x){
     if(x<0) putchar('-'),x=-x;
     if(x>9) write(x/10);
     putchar(x%10+'0');
}

string s ;
bitset <N> f[N];
int n ;
ll ans ;

int main(){
    scanf("%d",&n);
    for(int i=1;i<=n;i++){
        cin>>s ;
        for (int j=0;j<n;j++) if(s[j]=='1') f[i][j+1]=1;
        f[i][i]=1;
    }

    for (int i=1;i<=n;i++)
    for (int j=1;j<=n;j++)
    if (f[j][i]) f[j]|=f[i];

    for(int i=1;i<=n;i++) ans+=f[i].count(); 
    printf("%lld",ans);
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
根据引用[1],dp[u][j]表示在u子树中选取恰好j个人时能获得的最大价值。而根据引用,该问题的时间复杂度为O(log2​104×nm)。 对于洛谷P2143 [JSOI2010] 巨额奖金问题,我们可以使用动态规划来解决。具体步骤如下: 1. 首先,我们需要构建一棵树来表示员工之间的关系。树的根节点表示公司的总经理,其他节点表示员工。每个节点都有一个权值,表示该员工的奖金金额。 2. 接下来,我们可以使用动态规划来计算每个节点的dp值。对于每个节点u,我们可以考虑两种情况: - 如果选择节点u,则dp[u][j] = dp[v][j-1] + value[u],其中v是u的子节点,value[u]表示节点u的奖金金额。 - 如果不选择节点u,则dp[u][j] = max(dp[v][j]),其中v是u的子节点。 3. 最后,我们可以通过遍历树的所有节点,计算出dp[u][j]的最大值,即为所求的巨额奖金。 下面是一个示例代码,演示了如何使用动态规划来解决洛谷P2143 [JSOI2010] 巨额奖金问题: ```python # 构建树的数据结构 class Node: def __init__(self, value): self.value = value self.children = [] # 动态规划求解最大奖金 def max_bonus(root, j): dp = [[0] * (j+1) for _ in range(len(root)+1)] def dfs(node): if not node: return for child in node.children: dfs(child) for k in range(j, 0, -1): dp[node.value][k] = max(dp[node.value][k], dp[node.value][k-1] + node.value) for child in node.children: for k in range(j, 0, -1): for l in range(k-1, -1, -1): dp[node.value][k] = max(dp[node.value][k], dp[node.value][k-l-1] + dp[child.value][l]) dfs(root) return dp[root.value][j] # 构建树 root = Node(1) root.children.append(Node(2)) root.children.append(Node(3)) root.children[0].children.append(Node(4)) root.children[0].children.append(Node(5)) root.children[1].children.append(Node(6)) # 求解最大奖金 j = 3 max_bonus_value = max_bonus(root, j) print("最大奖金为:", max_bonus_value) ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值