2020 CCPC Wannafly Winter Camp Day3 部分题解(ACEFG)

查看题目


A 黑色气球

题意:

n个气球,每个气球高度为正整数。给你每两个气球之间的高度和,还原出所有气球的高度,保证答案唯一。

解题思路:

签到题,因为高度的范围不大,直接枚举第一个气球的高度,检测其与第二,三个气球的关系是否合法。需注意因为答案唯一所以只有两个气球的时候高度肯定都为1.

#pragma GCC optimize(2)
#include 
#define inf 1000000000
#define Inf 223372036854775807
#define maxn 1005
using namespace std;
typedef long long ll;
const ll mod=998244353;
const double eps=1e-9;
const double PI=acos(-1.0);

int n, num[maxn], s[maxn][maxn], x;

int main()
{
    cin>>n;
    for (int i=1; i<=n; i++)
        for (int j=1; j<=n; j++)
            cin>>s[i][j];
    for (x=1; x<=100000; x++)
        if (s[1][2]-x+s[1][3]-x==s[2][3]) break;
    if (n==2) x=1;
    for (int i=1; i<=n; i++)
        cout<<abs(s[1][i]-x)<<" ";
    return 0;
}


C 无向图定向

题意:

给一个n个点m条边的无向图,要求你对每条边指定一个方向,使原图成为一个有向无环图,且最长路最短,输出最短的最长路。

解题思路:

有个结论,给点染色,有边直接相连的点颜色不能一样,然后答案就是最少染色数-1。
所以用状压dp或者直接深搜找出无向图的最少染色数就行了。
关于结论的证明:其本质就是“狄尔沃斯定理”,定理指出:对于任意有限偏序集,其最长链中元素的数目必等于其最小反链划分中反链的数目。
狄尔沃斯定理的证明可以自行百度。

#pragma GCC optimize(2)
#include 
#define inf 1000000000
#define Inf 223372036854775807
#define maxn 1005
using namespace std;
typedef long long ll;
const ll mod=998244353;
const double eps=1e-9;
const double PI=acos(-1.0);

vector<int> edge[maxn];
int n, m, ans=inf, col[maxn];

int check(int now, int c)
{
    for (auto to: edge[now])
        if (c==col[to]) return 0;
    return 1;
}

void dfs(int now, int res)
{
    if (now>n) {ans=min(res, ans); return ;}
    if (res>=ans) return ;
    for (int i=1; i<=res+1; i++)
    {
        if (!check(now, i)) continue;
        col[now]=i;
        if (i==res+1)
        {
            res++;
            dfs(now+1, res);
            res--;
        }
        else dfs(now+1, res);
        col[now]=0;
    }
}

int main()
{
    cin>>n>>m;
    for (int i=1, u, v; i<=m; i++)
    {
        cin>>u>>v;
        edge[u].push_back(v);
        edge[v].push_back(u);
    }
    dfs(1, 0);
    cout<<ans-1;
    return 0;
}


E 棋技哥

题意:

两人下棋,棋盘大小为n*m,每个格子要么是黑色的要么是白色的。两人轮流操作,操作者可以选择一个黑色格子,并将以其为右下角,棋盘左上角为左上角的矩形中的所有格子颜色翻转,若无黑格可选,则当前操作者输。问先手者会赢还是输。

解题思路:

签到+大力猜测题。由于每次翻转都会导致棋盘左上角被翻转,因此若左上角开局是黑色的,后手者操作完左上角一定还是黑色的,因此先手者一定胜。若为白则先手者操作完左上角一定为黑,则后手者必胜。所以直接判断左上角颜色就行了。

#include
#pragma GCC optimize(2)
using namespace std;
typedef long long ll;
#define inf 1e9+1
#define Inf 223372036854775807
#define maxn 1010
#define eps 1e-8
const ll mod=998244353;
 
string s[maxn];
int T, n, m;
 
int main()
{
    cin>>T;
    while (T--)
    {
        cin>>n>>m;
        for (int i=1; i<=n; i++)
            cin>>s[i];
        if (s[1][0]=='1') cout<<"call\n";
        else cout<<"aoligei\n";
    }
    return 0;
}

F 社团管理

题意:

有n个人站成一排,第i个人有一个权值ai,要把他们分成恰好k段并使每段内两两权值相同的对数之和最少。

解题思路:

决策单调性+整体二分转移+莫队
超出能力范围了,不是很清楚这样写算不算有点套路的东西?反正确实是想不到…
决策单调性的证明可以看这位大佬的博客
总的来说,发现决策单调性后就可以通过类似整体二分的方法。
假设最优转移点的区间是[L, R],寻找[l, r]区间内每个点的最优转移点时可以先寻找到区间中点mid的最优转移点p,然后区间内左半段的点的最优转移点肯定在[L, p],右半段的点的最优转移点肯定在[p+1, R]。然后答案的统计利用莫队的思想来实现。
具体可以看代码吧,感觉对这道题解法的理解还有待加深。

#pragma GCC optimize(3)
#include 
#define inf 1000000000
#define Inf 223372036854775807
#define maxn 100010
using namespace std;
typedef long long ll;
const ll mod=998244353;
const double eps=1e-9;
const double PI=acos(-1.0);

ll cnt[maxn], num[maxn], ans;
ll lp=1, rp=0, dp[21][maxn];
ll n, k;

void add(ll x)
{
    ans+=cnt[x];
    cnt[x]++;
}
void red(ll x)
{
    cnt[x]--;
    ans-=cnt[x];
}
void md(int l, int r)
{
    while (lp>l) add(num[--lp]);
    while (lp<l) red(num[lp++]);
    while (rp>r) red(num[rp--]);
    while (rp<r) add(num[++rp]);
}
void cal(int l, int r, int L, int R, int k)
{
    if (l>=r)
    {
        for (int i=L; i<=R; i++)
        {
            md(i, l);
            if (dp[k][l]>dp[k-1][i-1]+ans)
                dp[k][l]=dp[k-1][i-1]+ans;
        }
        return ;
    }
    int mid=(l+r)>>1, p=L;
    for (int i=L; i<=min(R, mid); i++)
    {
        md(i, mid);
        if (dp[k][mid]>dp[k-1][i-1]+ans)
            dp[k][mid]=dp[k-1][i-1]+ans, p=i;
    }
    cal(l, mid, L, p, k);
    cal(mid+1, r, p, R, k);
}

int main()
{
    cin>>n>>k;
    for (int i=1; i<=n; i++)
        cin>>num[i];
    memset(dp, 0x3f, sizeof(dp));
    for (int i=1; i<=n; i++)
    {
        add(num[++rp]);
        dp[1][i]=ans;
    }
    for (int i=2; i<=k; i++)
    {
        dp[i][0]=0;
        cal(1, n, 1, n, i);
    }
    cout<<dp[k][n];
    return 0;
}


G 火山哥周游世界

题意:

给一颗n个节点的树,其中有k个节点是需要到达的,求分别以1,2…n为起点时经过k个节点的路径长度(不需要回到起点)。

解题思路:

从简考虑,假设没有k这个条件,每个点都要求到达的情况。
假设起点固定,比如说1号点为起点,要求出此时的答案,稍加观察就可以发现,最优情况下应该是从1号点到以1为根的树中最深点的路径上的边走一遍,其它边走两遍,这个最多动手画一下就能感受出来。
也就是1号点的答案就是所有边长度和乘2再减去最深点的深度。对其它点也是如此,因此我们只要能找到以i号点为根的情况下最深点的深度,就可以求出答案。
如果我们先求出了一号点的答案,并且记录了各个点在以一号点为根的情况下的深度,我们就可以对答案进行转移。
稍加观察可以发现,点的最深路径可以转移给它的儿子。
第一种情况:如果这个儿子不在其父亲到最深点的路径上,那么以这个点为根时最深点一定仍是原来那个点(因为最深的路径又加上了一条边)。
第二种情况:如果这个儿子在其父亲到最深点的路径上,那么就需要判断之前的最深路径是否还是最深的(因为少了一条边),具体而言,我们除了要记录每个点往下最深点,还要记录次深点。
第二种情况可以参考下图,当由fi转移到i时最深路径的三种情况。
2020 CCPC Wannafly Winter Camp Day3 部分题解(ACEFG)_第1张图片
现在再加入k这个条件,不难发现如果我们先找出这k个点的“虚树”(这里可能不能叫虚树,应该是包含了这k个点以及他们之间两两路径的树),其它点的答案就等于其到“虚树”最近距离的点的答案加上两者之间的距离。
(实现过程:大力dfs

#include
#pragma GCC optimize(2)
using namespace std;
typedef long long ll;
#define inf 1e9+1
#define Inf 223372036854775807
#define maxn 501000
#define eps 1e-8
const ll mod=998244353;
 
struct bian
{
    ll to, v;
};
 
ll n, K, root, go[maxn], col[maxn], ans[maxn];
vector<bian> E[maxn], edge[maxn];
ll sdp[maxn], mxdp[maxn], sedp[maxn], mxdpt[maxn];
 
ll dfs(ll now, ll fa)
{//找出“虚树”,建边
    ll f=0;
    if (go[now]) f=1;
    for (auto i: E[now])
    {
        if (i.to==fa) continue;
        if (dfs(i.to, now))
        {
            f=1;
            edge[now].push_back((bian){i.to, i.v});
            edge[i.to].push_back((bian){now, i.v});
        }
    }
    if (f) go[now]=1;
    return f;
}
 
void dfs2(ll now, ll fa)
{//找出 不在虚树上的点 的 虚树上最近的点 是哪个
    if (go[now]) col[now]=now;
    for (auto i: E[now])
    {
        if (i.to==fa) continue;
        if (!go[i.to])
        {
            ans[i.to]=ans[now]+i.v;
            col[i.to]=col[now];
        }
        dfs2(i.to, now);
    }
}
 
void dfs3(ll now, ll fa)
{
    for (auto i: edge[now])
    {
        if (i.to==fa) continue;
        dfs3(i.to, now);
        sdp[now]+=sdp[i.to]+i.v;
        if (mxdp[i.to]+i.v>mxdp[now])
            sedp[now]=mxdp[now], mxdp[now]=mxdp[i.to]+i.v, mxdpt[now]=i.to;
        else if (mxdp[i.to]+i.v>sedp[now]) sedp[now]=mxdp[i.to]+i.v;
    }
}
 
void dfs4(ll now, ll fa)
{
    ans[now]=-mxdp[now];
    for (auto i: edge[now])
    {
        if (i.to==fa) continue;
        if (i.to!=mxdpt[now])
        {
            mxdpt[i.to]=now;
            mxdp[i.to]=mxdp[now]+i.v;
            dfs4(i.to, now);
            continue;
        }
        if (sedp[now]+i.v>sedp[i.to]) sedp[i.to]=sedp[now]+i.v;
        if (i.to==mxdpt[now])
        {
            if (mxdp[i.to]<=sedp[i.to])
            {
                mxdpt[i.to]=now;
                mxdp[i.to]=sedp[i.to];
                dfs4(i.to, now);
            }
            else
            {
                dfs4(i.to, now);
            }
        }
    }
}
 
int main()
{
    cin>>n>>K;
    for (ll i=1, u, v, w; i<n; i++)
    {
        cin>>u>>v>>w;
        E[u].push_back((bian){v, w});
        E[v].push_back((bian){u, w});
    }
    for (ll i=1, x; i<=K; i++)
    {
        cin>>x;
        go[x]=1;
        root=x;
    }
    dfs(root, 0);
    dfs2(root, 0);
    dfs3(root, 0);
    ans[root]=-mxdp[root];
    dfs4(root, 0);
    for (ll i=1; i<=n; i++)
    {
        if (go[i]) cout<<2*sdp[root]+ans[i]<<"\n";
        else cout<<ans[i]+2*sdp[root]+ans[col[i]]<<"\n";
    }
    return 0;
}

你可能感兴趣的:(2020 CCPC Wannafly Winter Camp Day3 部分题解(ACEFG))