洛谷 P2055 [ZJOI2009] 假期的宿舍(最大流)

题目链接

https://www.luogu.com.cn/problem/P2055

思路

在这里插入图片描述
根据样例。我们从源点 S S S向所有不回家的在校生和非在校生连一条容量为 1 1 1的边。我们从所有的在校生向汇点 T T T连一条容量为 1 1 1的边。再根据所有的认识关系,从不回家的在校生和非在校生中的点向在校生中的点连一条容量为 1 1 1的边。如上图所示。

我们可以求出从源点到汇点的最大流,如果最大流的值与源点 S S S向所有不回家的在校生和非在校生连边的数量相等,则存在一种方案,否则不存在方案。

代码

#include <bits/stdc++.h>

using namespace std;

#define int long long
#define double long double

typedef long long i64;
typedef unsigned long long u64;
typedef pair<int, int> pii;

const int N = 1e2 + 5, M = 6e4 + 5;
const int mod = 1e9 + 7;
const int inf = 0x3f3f3f3f3f3f3f3f;

int n, m, S, T;
int a[N], b[N];
int d[N];   // 点在分层图中的编号
int cur[N]; // 当前弧优化,存储从当前节点开始还需要遍历的点的编号
struct Edge
{
    int to, f, next; // 终点,容量,同起点的上一条边的编号
} edge[M];           // 边集
int head[N], idx;    // head[i],表示以i为起点的第一条边在边集数组的位置(编号)
void init()          // 初始化
{
    for (int i = 0; i < N; i++)
        head[i] = -1;
    idx = 0;
}
void add_edge(int u, int v, int f) // 加边,u起点,v终点,w容量
{
    edge[idx].to = v;         // 终点
    edge[idx].f = f;          // 权值
    edge[idx].next = head[u]; // 以u为起点上一条边的编号,也就是与这个边起点相同的上一条边的编号
    head[u] = idx++;          // 更新以u为起点上一条边的编号
}
bool bfs()
{
    queue<int> q;
    memset(d, -1, sizeof d);
    q.push(S);
    d[S] = 0;
    cur[S] = head[S];
    while (q.size())
    {
        int u = q.front();
        q.pop();
        for (int i = head[u]; i != -1; i = edge[i].next)
        {
            int j = edge[i].to;
            if (d[j] == -1 && edge[i].f)
            {
                d[j] = d[u] + 1;
                cur[j] = head[j];
                if (j == T)
                    return true;
                q.push(j);
            }
        }
    }
    return false;
}
// 从起点S到当前点u允许流过的最大流量为limit
int find(int u, int limit)
{
    if (u == T)
        return limit;
    int flow = 0;
    for (int i = cur[u]; i != -1 && flow < limit; i = edge[i].next)
    {
        cur[u] = i; // 当前弧优化
        int j = edge[i].to;
        if (d[j] == d[u] + 1 && edge[i].f)
        {
            int op = find(j, min(edge[i].f, limit - flow));
            // 废点优化
            // op==0 说明不能找到从j到T的增广路
            // 因此下一次遍历到j时直接跳过即可, 将d[j]标记为-1即可
            if (!op)
                d[j] = -1;
            edge[i].f -= op;
            edge[i ^ 1].f += op;
            flow += op;
        }
    }
    return flow;
}
int dinic()
{
    int ans = 0, flow;
    while (bfs())
    {
        while (flow = find(S, inf))
            ans += flow;
    }
    return ans;
}
void solve(int test_case)
{
    cin >> n;
    init();
    S = 0, T = n * 2 + 1;
    int cnt = 0;
    for (int i = 1; i <= n; i++)
    {
        cin >> a[i];
    }
    for (int i = 1; i <= n; i++)
    {
        cin >> b[i];
    }
    for (int i = 1; i <= n; i++)
    {
        if ((a[i] == 1 && b[i] == 0) || a[i] == 0)
        {
            add_edge(i, n + i, 1);
            add_edge(n + i, i, 0);
            add_edge(S, i, 1);
            add_edge(i, S, 0);
            cnt++;
        }
        if (a[i] == 1)
        {
            add_edge(n + i, T, 1);
            add_edge(T, n + i, 0);
        }
    }
    for (int i = 1, x; i <= n; i++)
    {
        for (int j = 1; j <= n; j++)
        {
            cin >> x;
            if (x == 1)
            {
                if ((a[i] == 1 && b[i] == 0) || a[i] == 0)
                {
                    add_edge(i, n + j, 1);
                    add_edge(n + j, i, 0);
                }
            }
        }
    }
    int ans = dinic();
    if (ans == cnt)
    {
        cout << "^_^" << endl;
    }
    else cout << "T_T" << endl;
}

signed main()
{
    ios::sync_with_stdio(false);
    cin.tie(0), cout.tie(0);
    int test = 1;
    cin >> test;
    for (int i = 1; i <= test; i++)
    {
        solve(i);
    }
    return 0;
}
### 关于 P1249 最大乘积问题的 C++ 解题思路 对于给定的一个正整数 `n` 和分割次数 `k`,目标是将 `n` 分割成 `k` 个部分使得这些部分的乘积最大化。这个问题可以通过动态规划来解决。 #### 动态规划的状态定义 设 `dp[i][j]` 表示前 `i` 位数字分成 `j` 段所能得到的最大乘积[^3]。 #### 初始化 - 对于只有一段的情况,即 `dp[i][1]` 就是从第1位到第i位组成的整个数值。 #### 状态转移方程 为了求解 `dp[i][j]` 的值,可以考虑最后一个切割位置 `p` (其中 `1 ≤ p < i`),则状态转移方程为: \[ dp[i][j]=\max(dp[p][j-1]*num(p+1,i)) \] 这里 `num(p+1,i)` 是指从第 `p+1` 到第 `i` 位所表示的子串对应的十进制数值。 #### 边界条件 当 `j=1` 或者 `i=j` 时,显然不需要进一步划分,因此可以直接赋初值;其他情况下通过上述公式计算得出结果。 下面是具体的代码实现: ```cpp #include<iostream> #include<string> using namespace std; const int MAX_N = 50; string s; long long f[MAX_N][MAX_N], num[MAX_N][MAX_N]; // 计算字符串s中从l到r之间的数字转换成long long型 void calc_num() { for (int l = 0; l < s.size(); ++l) for (int r = l; r < s.size(); ++r) { if (!l && !r) num[l][r] = s[l]-'0'; else num[l][r] = num[l][r-1]*10+s[r]-'0'; } } int main(){ int N, K; cin >> N >> K >> s; // 预处理每一段的数值 calc_num(); // 初始化边界情况 for(int i = 0; i<s.length();++i){ f[i+1][1]=num[0][i]; f[i+1][i+1]=f[i][i]*10+(s[i]-'0'); } // 填表过程 for(int j = 2;j<=K;++j)//枚举段数 for(int i = j;i<N;++i)//枚举终点 for(int k = j-1;k<i;++k)// 枚举上一次切分的位置 f[i][j]=max(f[i][j],f[k][j-1]*num[k][i]); cout << f[N-1][K]<<endl; } ``` 这段代码实现了基于动态规划的方法来寻找最优解,并且能够有效地处理高精度运算的需求。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值