2023 ICPC 网络赛 第二场 部分题解 (待完善)

D Project Manhattan

思路:

最终选中的下标,必然包含n个行或者n个列.

所以答案 = n行的最小值之和或者n列的最小值之和

注意坑点: 当存在负数时,应该把负数全部选上,答案只会更优.

代码:

#include <bits/stdc++.h>
typedef long long ll;
typedef unsigned long long ull;
#define int long long
#define endl '\n'
#define bit(x) (1ll << x)
using namespace std;
const int N = 1e6 + 5;
const int inf = 1e16;
void solve()
{
    int n;
    cin >> n;
    vector<vector<int>> a(n + 1, vector<int>(n + 1));
    for (int i = 1; i <= n; i++)
    {
        for (int j = 1; j <= n; j++)
        {
            cin >> a[i][j];
        }
    }

    int ans1 = 0;
    int ans2 = 0;
    for (int i = 1; i <= n; i++)
    {
        vector<int> c(n + 1);
        for (int j = 1; j <= n; j++)
        {
            c[j] = a[i][j];
        }
        sort(c.begin() + 1, c.end());
        ans1 += c[1];
        int now = 2;
        while (c[now] < 0)
        {
            ans1 += c[now];
            now++;
        }
    }

    for (int i = 1; i <= n; i++)
    {
        vector<int> c(n + 1);
        for (int j = 1; j <= n; j++)
        {
            c[j] = a[j][i];
        }
        sort(c.begin() + 1, c.end());
        ans2 += c[1];
        int now = 2;
        while (c[now] < 0)
        {
            ans2 += c[now];
            now++;
        }
    }
    cout << min(ans1,ans2) << endl;
}
signed main()
{
    ios_base::sync_with_stdio(0);
    cin.tie(0);
    cout.tie(0);
    int t = 1;
    cin >> t;
    while (t--)
    {
        solve();
    }
    return 0;
}

E Another Bus Route Problem

题解:

下面这些话比较抽象, 具体看代码.

记忆化BFS.

对于一条路径(u,v), 不断的找他们的LCA,并标记中途经过的点,如果途中经过的点是线路的话,就加入队列中,然后把步数+1即可.

会有两种情况:

1.整条路径(u,v)都没有被访问过:直接标记即可

2.找LCA的过程中,已经有顶点被标记过了.那么说明他们的LCA也必然被标记过了,否则不可能出现这种状态.没被标记的那个条路径继续往上找.

#include <bits/stdc++.h>
typedef long long ll;
typedef unsigned long long ull;
#define int long long
#define endl '\n'
#define bit(x) (1ll << x)
using namespace std;
const int N = 1e6 + 5;
const int inf = 1e16;
vector<int> g[N];
vector<int> fa(N);
vector<int> dep(N);
map<int, vector<pair<int, int>>> path;
vector<int> vis(N); // 标记
void dfs(int u)
{
    dep[u] = dep[fa[u]] + 1;
    for (auto v : g[u])
    {
        fa[v] = u;
        dfs(v);
    }
}
void solve()
{
    int n, m;
    cin >> n >> m;
    for (int i = 2; i <= n; i++)
    {
        int x;
        cin >> x;
        g[x].push_back(i);
    }
    dfs(1);//LCA需要得到每个顶点的深度,
    for (int i = 1; i <= m; i++)//把路径存到两个端点上
    {
        int u, v;
        cin >> u >> v;
        path[u].push_back({u, v});
        path[v].push_back({u, v});
    }
    queue<array<int, 3>> q;
    for (int i = 0; i < path[1].size(); i++)//起点肯定是从1号开始的
    {
        q.push({path[1][i].first, path[1][i].second, 1});
    }
    vis[0] = 1e5;//无效状态,直接标记
    while (q.size())
    {
        int left = q.front()[0];
        int right = q.front()[1];
        int step = q.front()[2];//到当前路径(left,right)的转站步数
        q.pop();
        while (left != right && !vis[left] && !vis[right])//找LCA,如果出现已经访问过的点,直接停止
        {
            if (dep[left] < dep[right])
            {
                vis[right] = step;//标记路径
                for (int i = 0; i < path[right].size(); i++)//该点存在路径直接存进去
                {
                    q.push({path[right][i].first, path[right][i].second, step + 1});
                }
                right = fa[right];//往上走
            }
            else
            {
                vis[left] = step;
                for (int i = 0; i < path[left].size() ; i++)
                {
                    q.push({path[left][i].first, path[left][i].second, step + 1});
                }
                left = fa[left];
            }
        }
        //如果还没到LCA就终止了,说明他们的LCA已经被访问过了.
        while (!vis[left])//如果没被访问过,就一直往上标记,到达LCA或标记过的点就会停止
        {
            vis[left] = step;
            for (int i = 0; i < path[left].size() ; i++)
            {

                q.push({path[left][i].first, path[left][i].second, step + 1});
            }
            left = fa[left];
        }
        while (!vis[right])//如果没被访问过,就一直往上找
        {
            vis[right] = step;
            for (int i = 0; i < path[right].size() ; i++)
            {

                q.push({path[right][i].first, path[right][i].second, step + 1});
            }
            right = fa[right];
        }
    }
    for (int i = 2; i <= n; i++)
    {
        if (!vis[i])
        {
            cout << -1 << " ";
            continue;
        }
        cout << vis[i] << " ";
    }
    cout << endl;
}
signed main()
{
    ios_base::sync_with_stdio(0);
    cin.tie(0);
    cout.tie(0);
    int t = 1;
    // cin >> t;
    while (t--)
    {
        solve();
    }
    return 0;
}

M Dirty Work

题解:

写完一道题的期望时间是 ai + bi*pi

把n道题的期望时间从小到大排序,然后按顺序完成题目即可.

#include <bits/stdc++.h>
typedef long long ll;
typedef unsigned long long ull;
#define int long long
#define endl '\n'
#define bit(x) (1ll << x)
using namespace std;
const int N = 1e6 + 5;
const int inf = 1e16;
void solve()
{
    int n;
    cin >> n;
    vector<double> ans(n+1);
    for(int i = 1; i<=n; i++)
    {
        double a,b,p;
        cin >> a >> b >> p;
        ans[i] = a + 1.0*b*p;
    }
    sort(ans.begin()+1,ans.end());
    double sum =0;
    for(int i=1; i<=n; i++)
    {
        ans[i]+=ans[i-1];
        sum+=ans[i];
    }
    printf("%.10lf\n",sum);
}
signed main()
{
    int t = 1;
    cin >> t;
    while (t--)
    {
        solve();
    }
    return 0;
}

I Impatient Patient

题解:

标签:期望方程.

设dp[i]表示从i直接到达终点的期望天数

1. 成功的概率是 pi,直接到达终点

贡献是 :pi*1

2.失败的概率是 1-pi,那么从i点回到a[i]点,然后从a[i]点再到 i号点

贡献是 :(1-pi) * (i- a[i] +1 + dp[i])

综上,得到期望方程

dp[i] = pi + (1-pi) * (i- a[i] +1 + dp[i])

化简得:   dp[i] = ( p + (1-p)*(i-a[i]+1) )  /p;  

#include <bits/stdc++.h>
typedef long long ll;
typedef unsigned long long ull;
#define int long long
#define endl '\n'
#define bit(x) (1ll << x)
using namespace std;
const int N = 1e6 + 5;
const int inf = 1e16;

void solve()
{
    int n;
    cin >> n;
    vector<int> a(n + 2), q(n + 2);
    for (int i = 1; i <= n; i++)
    {
        cin >> a[i];
        a[i]++;
    }
    for (int i = 1; i <= n; i++)
    {
        cin >> q[i];
    }
    vector<double> dp(n + 2);
    double ans = n;
    for (int i = 1; i <= n; i++)
    {
        double p = q[i]/(1.0*100000);//成功概率
        dp[i] = ( p + (1-p)*(i-a[i]+1) )  /p;//从i到n+1点的期望天数
        ans = min(ans,i-1+dp[i]);
    }
    printf("%.12lf\n", ans);
}
signed main()
{
    int t = 1;
    cin >> t;
    while (t--)
    {
        solve();
    }
    return 0;
}

L Super-palindrome

题解:

先陈述一下最小公共前后缀的概念

比如字符串 :abc]defabcabcdef abc 

该字符串的最小公共前后缀为3:[abc] defabcabcdef [abc]  

字符串: ababababab

该字符串的最小公共前后缀为2: [ab]ababab[ab]

样例字符串: pbbp

该字符串的最小公共前后缀为2: [pb][pb]

设dp[l][r]表示区间[l,r] 是否是超级字符串.

nxt[id][i]表示在字符串 s[id]...s[n]中 以第i个字母为右端点的最小公共前后缀.

转移方程:

dp[l][r] |= dp[l+ Min][r-Min];  //其中 Min表示 字符串s [l,r]的最小公共前后缀.

举例说明:

样例说明:

问字符串[pb]pbpb[pb]是否是超级回文串?

最小公共前后缀[pb]可以成功匹配,所以只要看里面 pbpb 是否是超级回文串即可.

代码:

#include <bits/stdc++.h>
typedef long long ll;
typedef unsigned long long ull;
#define int long long
#define endl '\n'
#define bit(x) (1ll << x)
using namespace std;
const int N = 1e6 + 5;
const int inf = 1e16;
const int maxn = 5e3 + 10;
// nxt数组表示 以i为右端点的最短公共前后缀
int nxt[maxn][maxn];
void Min_kmp(string a, int id) // 传入的下标一定是从0开始的
{

    a = " " + a;
    int n = a.size() - 1;
    for (int i = 0; i <= n; i++)
    {
        nxt[id][i] = 0;
    }
    for (int i = 2, j = 0; i <= n; i++)
    {
        while (j && a[i] != a[j + 1])
            j = nxt[id][j];
        if (a[i] == a[j + 1])
            j++;
        nxt[id][i] = j;
    }
    for (int i = 2, j = 2; i <= n; i++, j = i)
    {
        while (nxt[id][j])
            j = nxt[id][j];
        if (nxt[id][i])
            nxt[id][i] = j;
    }
    for (int i = 0; i < n; i++)
    {
        nxt[id][i] = nxt[id][i + 1];
    }
    nxt[id][n] = 0;
}
int dp[maxn][maxn];
void solve()
{

    string s;
    cin >> s;
    int n = s.size();
    for(int i = 0; i<=n; i++)
    {
        for(int j = 0; j<=n; j++)
        {
            dp[i][j] = 0;
        }
    }
    for (int i = 0; i < s.size(); i++)
    {
        string t = s.substr(i);
        Min_kmp(t, i);//找到以s[i]为起点字符串的最小公共前后缀
    }
    int ans = 0;
    for (int len = 2; len <= n; len++)
    {
        for (int l = 0; l < n; l++)
        {
            int r = l + len - 1;
            int Min = nxt[l][r - l];//以s[l...r]中以r为右端点的最小公共前后缀
            if (Min * 2 == len)//直接拼接即可
            {
                dp[l][r] |= 1;
            }
            dp[l][r] |= dp[l + Min][r - Min];
            if (dp[l][r])
            {
                ans++;
            }
        }
    }
    cout << ans << endl;
}
signed main()
{
    ios_base::sync_with_stdio(0);
    cin.tie(0);
    cout.tie(0);
    int t = 1;
    cin >> t;
    while (t--)
    {
        solve();
    }
    return 0;
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值