Codeforces Round #635 (Div. 1)

A. Linova and Kingdom (CF 1336 A)

题目大意

给定一棵\(n\)个节点(从\(1\)开始)的树,\(1\)号节点为根。每个节点要么是工厂要么是旅游景点(包括根)。给定一个数\(k\),表示将\(k\)个节点变为工厂,最大化每个工厂到根节点的路径上的旅游景点数量的和。

解题思路

很显然可以贪心,但是我们对于工厂位置的选择很显然从叶子节点开始,但稍加思索下可以发现难以抉择,在状态转移的时候比较复杂。

我们可以反过来,考虑旅游景点的选择。很显然我们从根节点开始考虑。当我们选择一个节点设为旅游景点的时候,最优情况下,这个节点到根节点中的所有点一定不是工厂。

基于这个事实,我们可以很容易算出,一个节点\(a\)设为旅游景点的话,它对答案的贡献即为\(son[a]-deep[a]\),其中\(son[a]\)表示以\(a\)为根节点的子树的孩子数量(不包括它本身),\(deep[a]\)表示节点\(a\)的深度(从\(0\)开始)。

这两个数组一遍\(DFS\)即可求出,然后排序,前\(n-k\)大的数的和即为答案。

神奇的代码
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
template <typename T>
void read(T &x) {
    int s = 0, c = getchar();
    x = 0;
    while (isspace(c)) c = getchar();
    if (c == 45) s = 1, c = getchar();
    while (isdigit(c)) x = (x << 3) + (x << 1) + (c ^ 48), c = getchar();
    if (s) x = -x;
}

template <typename T>
void write(T x, char c = ' ') {
    int b[40], l = 0;
    if (x < 0) putchar(45), x = -x;
    while (x > 0) b[l++] = x % 10, x /= 10;
    if (!l) putchar(48);
    while (l) putchar(b[--l] | 48);
    putchar(c);
}

void DFS(int u,int fa,int deep[],int son[],vector<int> edge[]){
    for(auto v:edge[u]){
        if (v==fa) continue;
        deep[v]=deep[u]+1;
        ++son[u];
        DFS(v,u,deep,son,edge);
        son[u]+=son[v];
    }
}

int main(void) {
    int n,k;
    read(n);
    read(k);
    vector<int> edge[n+1];
    for(int u,v,i=1;i<n;++i){
        read(u);
        read(v);
        edge[u].push_back(v);
        edge[v].push_back(u);
    }
    int deep[n+1]={0};
    int son[n+1]={0};
    DFS(1,1,deep,son,edge);
    vector<int> val;
    for(int i=1;i<=n;++i){
        val.push_back(son[i]-deep[i]);
    }
    sort(val.begin(),val.end(),greater<int>());
    LL ans=0;
    for(int i=0;i<n-k;++i) ans+=(LL)val[i];
    write(ans,'\n');
    return 0;
}


B. Xenia and Colorful Gems (CF 1336 B)

题目大意

给定三组数,要求从每组数中选择一个数\(x,y,z\),使得这三个数的俩俩差的平方和最小。即求\(\min{(x-y)^2+(y-z)^2+(z-x)^2}\)

解题思路

三个数从小到大排序有左中右三种情况。

那么我们枚举\(a\)组取出来的数是中间的,\(b\)组是最小的,\(c\)组是最大的。

然后枚举\(a\)组里的数\(x\),找出\(b\)组里第一个小于等于\(x\)的数\(y\),找出\(c\)组里第一个大于等于\(x\)的数\(z\),计算它们俩俩差的平方和取最小值即可。

其实找\(x\)\(y\)可以不用\(lower\_bound\)用俩指针扫也行。

神奇的代码
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
template <typename T>
void read(T &x) {
    int s = 0, c = getchar();
    x = 0;
    while (isspace(c)) c = getchar();
    if (c == 45) s = 1, c = getchar();
    while (isdigit(c)) x = (x << 3) + (x << 1) + (c ^ 48), c = getchar();
    if (s) x = -x;
}

template <typename T>
void write(T x, char c = ' ') {
    int b[40], l = 0;
    if (x < 0) putchar(45), x = -x;
    while (x > 0) b[l++] = x % 10, x /= 10;
    if (!l) putchar(48);
    while (l) putchar(b[--l] | 48);
    putchar(c);
}

LL cal(LL a,LL b,LL c){
    return (a-b)*(a-b)+(a-c)*(a-c)+(b-c)*(b-c);
}

void work(vector<LL> num[],int a,int b,int c,LL &ans){
    for(auto i:num[a]){
        auto l=upper_bound(num[b].begin(),num[b].end(),i);
        if (l==num[b].begin()) continue;
        --l;
        auto r=lower_bound(num[c].begin(),num[c].end(),i);
        if (r==num[c].end()) continue;
        ans=ans==-1?cal(i,*l,*r):min(ans,cal(i,*l,*r));
    }
}

int main(void) {
    int t;
    read(t);
    while(t--){
        LL ans=-1;
        int cnt[3]={0};
        for(int i=0;i<3;++i) read(cnt[i]);
        vector<LL> num[3];
        for(int i=0;i<3;++i){
            for(int u,j=0;j<cnt[i];++j){
                read(u);
                num[i].push_back((LL)u);
            }
            sort(num[i].begin(),num[i].end());
        }
        work(num,0,1,2,ans);
        work(num,0,2,1,ans);
        work(num,1,0,2,ans);
        work(num,1,2,0,ans);
        work(num,2,0,1,ans);
        work(num,2,1,0,ans);
        write(ans,'\n');
    }
    return 0;
}


C. Kaavi and Magic Spell (CF 1336 C)

题目大意

给定两个字符串\(S、T\),要求通过以下两个操作来构造一个字符串\(A\)

  • \(S\)的首字符放到\(A\)的首部
  • \(S\)的首字符放到\(A\)的尾部

可以进行的操作数小于等于\(n\)\(n\)为串\(S\)的长度。

如果串\(T\)是串\(A\)的前缀,我们说串\(A\)是具有膜法的。问你有多少种方法去构造一个具有膜法的串\(A\)。答案模\(998244353\)

解题思路

如果我们从首字母开始考虑构造,当字母放到首部的时候,会对我们判断串\(T\)是否为串\(A\)的前缀有很大影响,还会发现转移具有后效性,我们得换个方向取考虑。

从最后一个字母来考虑的话,由于构造的串\(A\)的长度是已知的,这个字母只能放在当前考虑区间的首部或尾部,然后我们就可以缩小考虑范围继续考虑下一个字母,是同样的子问题。

\(dp[l][r]\)表示构造串\(A\)\([l...r)\)(从\(0\)开始)的所有方法中,满足串\(A\)是膜法的方法的数量。

转移就很简单了。

对于当前考虑的字母\(S[pos]\),如果\(l>m-1\)或者\(S[pos]==T[l]\)(即当前位不会影响判断串\(T\)是否为\(A\)的前缀或者填了是保持串\(T\)是串\(A\)的前缀的必要条件),则字母\(S[pos]\)可以放在第\(l\)位,此时\(dp[l][r]+=dp[l+1][r]\)

如果\(r>m\)或者\(S[pos]==T[r-1]\)(即当前位不会影响判断串\(T\)是否为\(A\)的前缀或者填了是保持串\(T\)是串\(A\)的前缀的必要条件),则字母\(S[pos]\)可以放在第\(r-1\)位,此时\(dp[l][r]+=dp[l][r-1]\)

我们枚举操作数量\(i=m\)\(n\)\(m\)为串\(T\)的长度),求得对应的\(dp[0][i]\)求和即是答案。

然而时间复杂度为\(O(n^{3})\)会炸。

因为求一次\(DP\)的时间复杂度为\(O(n^2)\)是已经是它的下限了,我们只能从枚举操作数量去优化。

我们发现,如果当操作数量为\(i\)时,求到了一些能使串\(A\)具有膜法的方法数量,当操作数变为\(i+1\)时,我们把额外的那个字母放到串\(A\)的最后面,以前求到的方法仍然能够使得串\(A\)具有膜法。当然放到前面我们就得重新算了。

那么我们可以直接算\(dp[0][n]\),在算的时候,我们记录一个数\(cnt\),表示我们把前\(cnt\)个字母都放到了后面,第\(cnt+1\)个字母放到了前面,这样,由于我们是从最后考虑的字母,这前\(cnt\)个字母显得“可有可无”,它们存不存在不会对我们的判断造成影响,而是会使得答案翻倍(当然\(cnt<n-m\))。

当我们知道有\(cnt\)个字母放到了后面,而目前考虑的区间的答案已经知道为\(dp[l][r]\),那么这\(cnt\)个字母的有无(也就是说操作数的减少)就会产生额外的答案为\(dp[l][r]*min(cnt,n-m)\)。(有没有它们,能够产生膜法串\(A\)的方法数都是\(dp[l][r]\))。

时间复杂度就变为\(O(n^{2})\)

我傻了,求出\(dp[0][n]\)\(\sum\limits_{i=m}^{n}dp[0][i]\)就是答案。

代码里的ans就是划线处的体现。

神奇的代码
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;

const LL mo=998244353;

char s[3006],t[3006];

int ls,lt;

LL dp[3006][3006];

LL ans;

LL DP(int pos,int l,int r,int id,int cntt){
    if (l==r) {
        ans=(ans+min(id,ls-lt))%mo;;
        return 1;
    }
    if (dp[l][r]!=-1) {
        ans=(ans+dp[l][r]*min(id,ls-lt))%mo;
        return dp[l][r];
    }
    LL qwq=0;
    if (l>lt-1||s[pos]==t[l]) qwq=(qwq+DP(pos-1,l+1,r,id==12345?cntt:id,cntt+1))%mo;
    if (r>lt||s[pos]==t[r-1]) qwq=(qwq+DP(pos-1,l,r-1,id,cntt+1))%mo;
    return dp[l][r]=qwq;
}

int main(void) {
    scanf("%s%s",s,t);
    memset(dp,-1,sizeof(dp));
    ls=strlen(s);
    lt=strlen(t);
    ans=(ans+DP(ls-1,0,ls,12345,0))%mo;
    LL qwq=0;
    for(int i=lt;i<=ls;++i)
        qwq=(qwq+dp[0][i])%mo;
    printf("%lld\n",qwq);
    return 0;
}


\(tourist翻了(\)

posted @ 2020-04-16 17:01  ~Lanly~  阅读(373)  评论(0编辑  收藏  举报