VK Cup 2017

和FallDream组队瞎打一通……B两个人写的都挂了233,最后只剩下FallDream写的A和我写的C,最后我yy了个E靠谱做法结果打挂了,结束之后改了改就A了,难受。

AC:AC Rank:180 Rating:2133-8->2125

A.Bear and Friendship Condition

题目大意;问一个无向图是否满足若a到b有边且b到c有边则a到c有边。(n<=150,000)

思路:判定每个连通块是不是团,不是则不满足,复杂度O(n)。

#include<cstdio>
inline int read()
{
    int x;char c;
    while((c=getchar())<'0'||c>'9');
    for(x=c-'0';(c=getchar())>='0'&&c<='9';)x=(x<<3)+(x<<1)+c-'0';
    return x;
}
#define MN 150000
struct edge{int nx,t;}e[MN*2+5];
int h[MN+5],en,r[MN+5],u[MN+5];
long long sum,cnt;
inline void ins(int x,int y)
{
    e[++en]=(edge){h[x],y};h[x]=en;++r[x];
    e[++en]=(edge){h[y],x};h[y]=en;++r[y];
}
void dfs(int x)
{
    cnt+=u[x]=1;sum+=r[x];
    for(int i=h[x];i;i=e[i].nx)if(!u[e[i].t])dfs(e[i].t);
}
int main()
{
    int n,m,x,y,i;
    n=read();m=read();
    while(m--)ins(read(),read());
    for(i=1;i<=n;++i)if(!u[i])
    {
        sum=cnt=0;dfs(i);
        if(cnt*(cnt-1)!=sum)return 0*puts("NO");
    }
    puts("YES");
}

B.Bear and Different Names

题目大意:给定n,k和n-k+1个NO/YES,第i个NO/YES表示第i~i+k-1个字符串有/没有重复的字符串,要求构造一种合法方案。(k<=n<=50)

思路:倒着构造,先把后k-1个弄成各不相同,倒着每碰到一个NO就把第i个和第i+k-1个弄成相同的,否则弄成不同的。

#include<iostream>
using namespace std;
#define MN 50
int f[MN+5],t[MN+5];
string build(int x)
{
    string s=x<26?"A":"B";
    return s+=x%26+'a';
}
int main()
{
    int n,k,i;string s;
    cin>>n>>k;
    for(i=1;i<n;++i)f[i]=i;
    for(i=0;i<n-k+1;++i)cin>>s,t[i]=s=="NO";
    while(i--)if(t[i])f[i]=f[i+k-1];
    for(i=0;i<n;++i)cout<<build(f[i])<<' ';
}

C.Bear and Tree Jumps

题目大意:给定一棵n个点的树,f(s,t)表示从s走到t,一次最多走k步,最少走几次,求Σf(i,j) (i<j)。(n<=200,000,k<=5)

思路:树形DP,ff[i]表示子树i内,所有节点走到根,共走了几个整的k步,f[i][j]表示子树i内,所有节点走到根,除去走的整的k步,剩下一步走了j的点有几个,每次合并子树信息并统计答案即可,复杂度O(n*k^2)。

#include<cstdio>
#include<iostream>
using namespace std;
char B[1<<26],*S=B,C;int X;
inline int read()
{
    while((C=*S++)<'0'||C>'9');
    for(X=C-'0';(C=*S++)>='0'&&C<='9';)X=(X<<3)+(X<<1)+C-'0';
    return X;
}
#define MN 200000
struct edge{int nx,t;}e[MN*2+5];
int k,h[MN+5],en,f[MN+5][5];
long long ans,ff[MN+5];
inline void ins(int x,int y)
{
    e[++en]=(edge){h[x],y};h[x]=en;
    e[++en]=(edge){h[y],x};h[y]=en;
}
void dfs(int x,int fa)
{
    int i,j,l;
    f[x][0]=1;
    for(i=h[x];i;i=e[i].nx)if(e[i].t!=fa)
    {
        dfs(e[i].t,x);
        for(j=0;j<k;++j)
        {
            ans+=1LL*f[e[i].t][j]*ff[x]+1LL*f[x][j]*ff[e[i].t];
            for(l=0;l<k;++l)ans+=1LL*f[e[i].t][j]*f[x][l]*((j+l)/k+1);
        }
        for(j=1;j<k;++j)f[x][j]+=f[e[i].t][j-1];
        f[x][0]+=f[e[i].t][k-1];ff[x]+=ff[e[i].t]+f[e[i].t][k-1];
    }
}
int main()
{
    fread(B,1,1<<26,stdin);
    int n=read(),i;k=read();
    for(i=1;i<n;++i)ins(read(),read());
    dfs(1,0);
    cout<<ans;
}

D.Bear and Company

题目大意:给定一个字符串,问你至少交换多少次相邻字符,才能使得字符串中不出现相邻的“VK”。(字符串长度<=75)

思路:用0表示V,用1表示K,用2表示其他字符,容易得出最后交换完,0,1,2各自的相对顺序不会改变,我们用f[i][j][k][0/1]表示最终答案的前i+j+k个由前i个0,前j个1,前k个2组成,最后一个是不是V时的最小花费,每次我们往状态里加一个字符,我们统计另外两种字符已加入状态的有多少个在原串中位置大于该字符,即为加入该字符的花费。复杂度O(飞快)。

#include<cstdio>
#include<vector>
#include<algorithm>
using namespace std;
#define MN 75
#define INF 0x3FFFFFFF
char s[MN+5];
vector<int> v[3];
int f[MN+5][MN+5][MN+5][2];
int main()
{
    int n,i,j,k,l,cnt;
    scanf("%d%s",&n,s);
    for(i=0;i<n;++i)
        if(s[i]=='V')v[0].push_back(i);
        else if(s[i]=='K')v[1].push_back(i);
        else v[2].push_back(i);
    for(i=0;i<=v[0].size();++i)for(j=0;j<=v[1].size();++j)for(k=0;k<=v[2].size();++k)if(i||j||k)
    {
        f[i][j][k][0]=f[i][j][k][1]=INF;
        if(i)
        {
            f[i][j][k][1]=min(f[i-1][j][k][0],f[i-1][j][k][1]);
            for(l=0;l<j;++l)if(v[1][l]>v[0][i-1])++f[i][j][k][1];
            for(l=0;l<k;++l)if(v[2][l]>v[0][i-1])++f[i][j][k][1];
        }
        if(j)
        {
            cnt=f[i][j-1][k][0];
            for(l=0;l<i;++l)if(v[0][l]>v[1][j-1])++cnt;
            for(l=0;l<k;++l)if(v[2][l]>v[1][j-1])++cnt;
            f[i][j][k][0]=min(f[i][j][k][0],cnt);
        }
        if(k)
        {
            cnt=min(f[i][j][k-1][0],f[i][j][k-1][1]);
            for(l=0;l<i;++l)if(v[0][l]>v[2][k-1])++cnt;
            for(l=0;l<j;++l)if(v[1][l]>v[2][k-1])++cnt;
            f[i][j][k][0]=min(f[i][j][k][0],cnt);
        }
    }
    printf("%d",min(f[i-1][j-1][k-1][0],f[i-1][j-1][k-1][1]));
}

E.Bear and Rectangle Strips

题目大意:给定一个2*n的数字矩阵,问最多能选出多少个互不相交的子矩阵和都为0。(n<=300,000)

思路:预处理出每个i作为右端点时第一行/第二行/两行一起向左最近的和为0的矩阵的左端点,用f[i]表示前2*i的数字矩阵的答案,每次转移有两种情况,一种直接两行一起取一个和为0的矩阵,利用预处理的信息直接转移,另一种情况是两行分别取若干个一行的子矩阵,我们用d(i,j)表示第一行i左边任意取,第二行j左边任意取的最大收益,采用记忆化搜索的方式计算,容易发现我们排除了大量的重复计算和无用状态,最终复杂度是比较科学的O(能过)。

#include<cstdio>
#include<algorithm>
#include<map>
using namespace std;
#define ll long long
char B[1<<26],*S=B,C;ll X,F;
inline ll read()
{
    for(F=1;(C=*S++)<'0'||C>'9';)if(C=='-')F=-1;
    for(X=C-'0';(C=*S++)>='0'&&C<='9';)X=(X<<3)+(X<<1)+C-'0';
    return X*F;
}
#define MN 300000
#define d(x,y) make_pair(x,y)
ll s[MN+5][3];
int t[MN+5][3],f[MN+5];
map<ll,int> mp;
map<pair<int,int>,int> u,ff;
int cal(int x,int y)
{
    if(u[d(x,y)])return ff[d(x,y)];
    return u[d(x,y)]=1,ff[d(x,y)]=max(f[min(x,y)],t[x][0]>t[y][1]?cal(t[x][0],y)+1:cal(x,t[y][1])+1);
}
int main()
{
    fread(B,1,1<<26,stdin);
    int n=read()+1,i,j,k,l;
    for(i=2;i<=n;++i)s[i][0]=s[i-1][0]+read();
    for(i=2;i<=n;++i)s[i][1]=s[i-1][1]+read(),s[i][2]=s[i][0]+s[i][1];
    for(l=0;l<3;++l)for(mp.clear(),i=1;i<=n;++i)t[i][l]=max(t[i-1][l],mp[s[i][l]]),mp[s[i][l]]=i;
    f[0]=t[0][0]=t[0][1]=-1;u[d(0,0)]=1;ff[d(0,0)]=-2;
    for(i=2;i<=n;++i)f[i]=max(f[t[i][2]]+1,cal(i,i)),ff[d(i,i)]=f[i];
    printf("%d",f[n]);
}
原文地址:https://www.cnblogs.com/ditoly/p/VK-Cup-2017-R1.html