CODE FESTIVAL 2017 qual B

AB省略

C - 3 Steps
若u可以走3条边到v,则可以连边(u,v),发现就是将(u,v)间的距离由3变成1,同时所有经过u,v的路径长度减少了2,但奇偶性都没有改变,可以推得,最后u将会和所有它能走奇数距离到达的点连边
如果原图有奇环,u能够通过奇环调整,使得它与所有点连边
如果不存在奇环即是一个二分图,不同颜色的点对之间都会有边

code:

#include<set>
#include<map>
#include<deque>
#include<queue>
#include<stack>
#include<cmath>
#include<ctime>
#include<bitset>
#include<string>
#include<vector>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<climits>
#include<complex>
#include<iostream>
#include<algorithm>
#define ll long long
using namespace std;

const int maxn = 210000;

int n,m;
struct edge{int y,nex;}a[maxn<<1]; int len,fir[maxn];
inline void ins(const int x,const int y){a[++len]=(edge){y,fir[x]};fir[x]=len;}

int col[maxn],N[2];
bool color(const int x)
{
    bool re=true;
    for(int k=fir[x],y=a[k].y;k;k=a[k].nex,y=a[k].y)
    {
        if(col[y]==-1) N[col[y]=!col[x]]++,re=color(y);
        else if(col[y]!=!col[x]) re=false;
        if(!re) return re;
    }
    return re;
}

int main()
{
    memset(col,-1,sizeof col);
    scanf("%d%d",&n,&m);
    for(int i=1;i<=m;i++) 
    {
        int x,y; scanf("%d%d",&x,&y);
        ins(x,y); ins(y,x);
    }
    N[col[1]=0]++; bool flag=color(1);
    if(flag) printf("%lld\n",(ll)N[0]*N[1]-m);
    else printf("%lld\n",(ll)n*(n-1)/2ll-m);

    return 0;
}

D - 101 to 010
发现相隔超过1个0后两段答案互不影响,相互隔绝
对于一段内,一定是形如1111..011..01..的形式
同时发现101->010的变换,如果某一边的1另一边是0,合并完后就形成隔绝了
所以对于一段1,左边合并上来后就与左边的隔绝了,一定是一直合并完或者合并剩最右边的1(右边同理)。
注意到变换只和左1位是不是0,左2位是不是1有关(右同理),可以dp[i][0/1/2]表示处理了i前面(包含i)的合并,i前面第一个1离i的距离(>=2的都视作2)的最大次数……..然后我狂wa不止…转移的细节有点多…

然而好像直接dp[i]就可以做了,细节还少很多..

code:

#include<set>
#include<map>
#include<deque>
#include<queue>
#include<stack>
#include<cmath>
#include<ctime>
#include<bitset>
#include<string>
#include<vector>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<climits>
#include<complex>
#include<iostream>
#include<algorithm>
#define ll long long
#define inf 1e9
using namespace std;

inline void up(int &x,const int &y){if(x<y)x=y;}
const int maxn = 510000;

int n;
char str[maxn];

int f[maxn][3],g[maxn];
int solve(const int l,const int r)
{
    f[l-1][2]=0; g[l-1]=0;
    for(int i=l;i<=r;i++)
    {
        int gg=g[i-1]; up(g[i-1],f[i][2]);
        if(str[i]=='0')
        {
            up(f[i][1],f[i-1][0]);
            up(f[i][2],max(f[i-1][1],f[i-1][2]));
        }
        else
        {
            int j=i+1; for(;j<=r&&str[j]=='1';j++);j--;
            if(j!=i)
            {
                up(f[j][0],max(g[i-1],f[i-1][1]+j-i));
                up(f[j][1],f[i-1][1]+j-i+1);
                up(f[j][2],g[i-1]);
                if(j!=r) up(f[j+2][2],max(gg+j-i+1,g[i-1]+j-i));
                i=j;
            }
            else
            {
                up(f[i][0],gg);
                up(f[i][1],f[i-1][1]+1);
                up(f[i][2],g[i-1]);
            }
        }
        for(int j=0;j<3;j++) up(g[i],f[i][j]);
    }
    return max(g[r],0);
}

int main()
{
    scanf("%d",&n); scanf("%s",str+1);
    for(int i=0;i<=n;i++) for(int j=0;j<3;j++) f[i][j]=-inf;

    int ans=0;
    for(int i=1;i<=n;i++) if(str[i]=='1')
    {
        int j=i+1;
        for(int las=i;j<=n;j++)
        {
            if(str[j]=='1') las=j;
            if(j-las>=2) break;
        }j--;
        for(;str[j]=='0';j--);
        ans+=solve(i,j); i=j;
    }
    printf("%d\n",ans);

    return 0;
}

E - Popping Balls
懒得贴图了,你们看题解的图凑合吧qwq
将问题变成二维平面上,从点(A,B)到(0,0)的不同路径数,满足要求:任何时候都可以左移,只有x< s,x+y>=s或x< t,x+y>=t时才能向下移(相当于竖边只能在两个梯形内才能往下走)

对于计算答案,从(A,B)开始往左走,枚举第一次往下走的横坐标i,此时第二个梯形的底边为x=i时显然能最大化方案数,再枚举走到这个梯形斜边的哪个位置(a,i-a),从(i,m-1)到(a,i-a)的路径没有限制,可以直接组合数,从(a,i-a)到(0,0)的合法路径数可以仿照上述过程,做个dp求

code:

#include<set>
#include<map>
#include<deque>
#include<queue>
#include<stack>
#include<cmath>
#include<ctime>
#include<bitset>
#include<string>
#include<vector>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<climits>
#include<complex>
#include<iostream>
#include<algorithm>
#define ll long long
using namespace std;

const ll Mod = 1e9+7;
const int maxn = 2100;

int n,m;
ll c[maxn][maxn],pc[maxn][maxn];
ll f[maxn][maxn],g[maxn][maxn];

int main()
{
    scanf("%d%d",&n,&m);
    c[0][0]=1ll; pc[0][0]=1ll;
    for(int i=1;i<maxn;i++)
    {
        c[i][0]=1ll; pc[i][0]=1ll;
        for(int j=1;j<=i;j++) 
            c[i][j]=(c[i-1][j-1]+c[i-1][j])%Mod,
            pc[i][j]=(pc[i][j-1]+c[i][j])%Mod;
    }
    for(int i=0;i<=n;i++) f[i][0]=g[i][0]=1ll;
    for(int i=0;i<=n;i++)
    {
        for(int j=1;j<=m;j++)
            f[i][j]=pc[j-1][min(i,j-1)];
    }
    for(int j=1;j<=m;j++)
    {
        g[0][j]=f[0][j];
        for(int i=1;i<=n;i++) g[i][j]=(g[i-1][j]+f[i][j])%Mod;
    }
    ll ans=1;
    for(int i=n;i>=1;i--)
    {
        for(int y=0,u=min(m-1,i);y<=u;y++)
        {
            int x=i-y; //(i,m-1) -> (x,y)
            ans+=g[x][y]*c[i+m-1-x-y][i-x]%Mod;
        }
    }
    ans%=Mod;
    printf("%lld\n",ans);

    return 0;
}

F - Largest Smallest Cyclic Shift
可能因为语言障碍?不知道题解在说什么…..然后膜了一个很神奇的代码
先将X个a,Y个b,Z个c放进multiset里面,每次取出最小和最大的字符串,将最大的接在最小的后面,塞回multiset,直到multiset里面只剩1个串,就是答案。

个人yy了一个很(不)严谨的证明:
用数学归纳法,假设每一步前都保证目标串由当前这些字符串组成,证明每一步操作后目标串仍由这些串组成
因为要最小表示最大,当前最小的字符串可以视为是目标串的开头,当然要让他尽量大,所以给他拼上当前最大的字符串,符合题目要求,所以目标串仍然由这些串组成 证明默认了最小的串唯一(唯一的情况证了,不唯一的情况类似,意会一下,结论仍然成立)
然后因为一开始每个串长度都是1的情况显然符合目标串由这些串组成,所以最后拼成的串就是目标串

code:

#include<set>
#include<map>
#include<deque>
#include<queue>
#include<stack>
#include<cmath>
#include<ctime>
#include<bitset>
#include<string>
#include<vector>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<climits>
#include<complex>
#include<iostream>
#include<algorithm>
#define ll long long
using namespace std;

int x,y,z;
multiset<string>S;
multiset<string>::iterator it;

int main()
{
    scanf("%d%d%d",&x,&y,&z);
    for(int i=1;i<=x;i++) S.insert("a");
    for(int i=1;i<=y;i++) S.insert("b");
    for(int i=1;i<=z;i++) S.insert("c");
    while(S.size()>1)
    {
        it=S.end(); it--;
        string ss=(*S.begin())+(*it);
        S.erase(S.begin());
        S.erase(it);
        S.insert(ss);
    }
    cout<<(*S.begin())<<endl;

    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值