diverta 2019 Programming Contest

C AB Substrings

#include<bits/stdc++.h>
using namespace std;
const int N=1e4+5;
int n;
int main()
{
    scanf("%d",&n);
    int ans=0,l=0,r=0,flag=false;
    for(int i=1;i<=n;i++)
    {
        char s[15];
        scanf("%s",s+1);
        int m=strlen(s+1);
        if(s[1]=='B') l++;
        if(s[m]=='A') r++;
        if(!(s[1]=='B'&&s[m]=='A')&&(s[1]=='B'||s[m]=='A')) flag=true;
        for(int i=1;i<m;i++)
            if(s[i]=='A'&&s[i+1]=='B') ans++;
    }
    ans+=min(l,r);
    if(!flag&&min(l,r)>0) ans--;
    printf("%d\n",ans);
}

D DivRem Number
考虑数论分块,这样的数在每个块里一定只有一个,并且它具有单调性,在每一块里面二分即可

#include<bits/stdc++.h>
#define ll long long
using namespace std;
int main()
{
    long long n,ans=0;
    scanf("%lld",&n);
    long long l=1,r;
    while(l<=n)
    {
        r=n/(n/l);
        ll x=l,y=r;
        while(x<=y)
        {
            ll m=x+y>>1;
            if(n/m==n%m) {ans+=m;break;}
            else if(n/m>n%m) y=m-1;
            else x=m+1;
        }
        l=r+1;
    }
    printf("%lld\n",ans);
}

E XOR Partitioning
首先,符合答案的一定是某一个异或前缀和,如果这个前缀和是满足 s u m x = z {sum_x=z} sumx=z

考虑一个划分,如果在位置 y {y} y可以分为两段,那么一定满足 s u m y = 0 {sum_y=0} sumy=0,因为 0 ⨁ z {0\bigoplus z} 0z等于 z {z} z,说明 x + 1 {x+1} x+1 y {y} y这一段的异或和为 z {z} z,处理前缀和之后,那么符合条件的序列一定是 z , 0 , z , 0 , z , 0... {z,0,z,0,z,0...} z,0,z,0,z,0...,把每一种情况的前缀和放入vector进行dp即可。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=5e5+5,mod=1e9+7;
int n,a[N],sum[N];
vector<int>v[1300000];
int f(int l,int r){return sum[r]-sum[l-1];}
int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
        scanf("%d",&a[i]),a[i]^=a[i-1];
    for(int i=1;i<=n;i++)
    {
        sum[i]=sum[i-1]+(a[i]==0);
        v[a[i]].push_back(i);
    }
    ll ans=0;
    for(int i=0;i<1<<20;i++)
    {
        if(!v[i].size()) continue;
        if(a[n]!=0&&a[n]!=i) continue;
        ll res=1,dp0=1,sumres=1;
        for(int j=1;j<v[i].size();j++)
        {
            if(i==0) dp0=res+1,res=(res+dp0)%mod;
            else
            {
                dp0=(dp0+sumres*f(v[i][j-1],v[i][j]))%mod;
                res=dp0;
                sumres=(sumres+res)%mod;
            }
        }
        if(a[n]==0&&i==0) ans=(ans+dp0)%mod;
        else if(a[n]==0) ans=(ans+sumres)%mod;
        else ans=(ans+res)%mod;
    }
    printf("%lld\n",ans);
}

F Edge Ordering
考虑一条边如果不是最小生成树中的边,那么这条边一定比它与最小生成树所形成环上的最大的边还要大,否则我们可以砍掉环中一个比它大的边,把这条更小的边连上去。

那么假设我们现在按权值从大到小加边,那么在加入一条生成树中的边之前,就要先把与这条边形成环的非生成树中的边加进来。

d p s t {dp_{st}} dpst表示边的状态为 s t {st} st的树边的总和, s i s t {si_{st}} sist表示边的状态达到 s t {st} st的方案数,我们可以通过简单的dp做出来,但是由于这样需要考虑所有的边集,时间复杂度会达到 O ( 2 n n ) {O(2^{n^n})} O(2nn)

我们考虑另外一种生成一个 1 , 2 , 3 , 4... {1,2,3,4...} 1,2,3,4...这样的升序排列的方法。

一开始排列为空,然后跑一下如图step1~5的步骤。

在这里插入图片描述
图中把每次加入的东西看作一条线,线选择一个位置后,从前往后延伸,最后各部分长度
叠加的部分就构成了一个排列。而发现这个线插入的位置可以决定第i各数的大小。

那么,考虑从大到小插入所有的边,如果是一条树边,我们只能插在最前面,如果是一条非树边,我们可以插在任意位置,step的虚线表示非树边可以插入的位置。

那么怎么维护权值呢,考虑当前状态为 n , b , c , s {n,b,c,s} n,b,c,s n {n} n表示加入的边数, b {b} b表示加入的树边的数量, c {c} c表示序列的种数, s {s} s表示所有 c {c} c种序列的树边的总和。

那么,如果加入一条树边 n , b , c , s − > n + 1 , b + 1 , c , s + c ∗ ( b + 1 ) {n,b,c,s->n+1,b+1,c,s+c*(b+1)} n,b,c,s>n+1,b+1,c,s+c(b+1),因为这个树边只能加在最前面,所以种类数不变,而它的线的长度为 b + 1 {b+1} b+1

如果加入非树边 n , b , c , s − > n + 1 , b , c ( n + 1 ) , s ( n + 2 ) {n,b,c,s->n+1,b,c(n+1),s(n+2)} n,b,c,s>n+1,b,c(n+1),s(n+2)。即非树边插入n+1个空位中任意一个,所以种类数乘了 n + 1 {n+1} n+1,已经插入的线段的总和变成了 s ( n + 1 ) {s(n+1)} s(n+1),至于剩下的这 s {s} s,因为我们怎么加入非树边也只统计树边的总和,所以虽然加入的非树边长度的总和大于树边的长度,但由于我们只统计树边的长度,所以非树边的长度对原长度的影响就是原长度的总和。

如图所示,虚线为非树边,非树边线段的长度虽然大于树边,但是多出的长度不影响总和。
在这里插入图片描述
于是根据这个转移,如果我们连续加入a条非树边,那么 n , b , c , s {n,b,c,s} n,b,c,s移成了
n + k , b , ( n + 1 ) ⋅ ⋅ ⋅ ( n + k ) c , ( n + 2 ) ⋅ ⋅ ⋅ ( n + k + 1 ) s {n + k, b,(n + 1)· · ·(n + k)c,(n + 2)· · ·(n + k + 1)s} n+k,b,(n+1)(n+k)c,(n+2)(n+k+1)s
因此假设加入树边i前要先加入 a i {a_i} ai条非树边,我们可以直接 O ( 1 ) {O(1)} O(1)转移。

那么回到之前设的 d p {dp} dp d p s t {dp_{st}} dpst表示边的状态为 s t {st} st的树边的总和, s i s t {si_{st}} sist表示边的状态达到 s t {st} st的方案数,此时我们不用考虑非树边的状态了,我们只要预处理要加入的数量,时间复杂度变成 O ( n 2 n ) {O(n2^n)} O(n2n),足够解决这道问题,注意 b {b} b直接被状态表示出来了,每个状态的 n {n} n也可以预处理出来。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=21,M=N*N,mod=1e9+7;
int n,m,up,sum[1<<20],e[1<<20],f[N],x[M],y[M],mp[N],bit[1<<20];
int Bit(int x){return x==0||bit[x]?bit[x]:bit[x]=__builtin_popcount(x);}
int getf(int x){return f[x]==x?x:f[x]=getf(f[x]);}
ll p[M],invp[M],dp[1<<20],si[1<<20],len[1<<20];
ll Dp()
{
    dp[up-1]=0;si[up-1]=1;
    for(int i=up-1;i>=1;i--)
    {
        for(int j=1;j<=n;j++)
            if(i>>j-1&1)
        {
            int st=i^(1<<j-1);
            int n=len[i],a=sum[i]-sum[st];
            len[st]=n+a+1;
            ll c=si[i]*p[n+a]%mod*invp[n]%mod;
            ll s=dp[i]*p[n+a+1]%mod*invp[n+1]%mod;
            s=(s+c*(Bit((up-1)^st)));
            (dp[st]+=s)%=mod;
            (si[st]+=c)%=mod;
        }
    }
    return dp[0];
}
int main()
{
    p[0]=p[1]=invp[0]=invp[1]=1;
    for(int i=2;i<M;i++) p[i]=p[i-1]*i%mod,invp[i]=(mod-mod/i)*invp[mod%i]%mod;
    for(int i=2;i<M;i++) invp[i]=invp[i-1]*invp[i]%mod;
    scanf("%d%d",&n,&m);
    for(int i=1;i<=m;i++)
    {
        scanf("%d%d",&x[i],&y[i]);
        mp[x[i]]|=1<<y[i]-1;
        mp[y[i]]|=1<<x[i]-1;
    }
    up=1<<n;
    for(int i=1;i<up;i++)
    {
        for(int j=1;j<=n;j++)
            if(i>>j-1&1)
            {
                e[i]=e[i^(1<<j-1)]+Bit(i&mp[j]);
                break;
            }
    }
    for(int i=1;i<up;i++)
        e[i]-=Bit(i)-1;
    up=1<<n-1;
    int st[N];
    for(int i=1;i<up;i++)
    {
        for(int j=1;j<=n;j++) f[j]=j;
        for(int j=1;j<n;j++)
            if(i>>j-1&1)
            f[getf(x[j])]=getf(y[j]);
        memset(st,0,sizeof(st));
        for(int j=1;j<=n;j++)
            st[getf(j)]|=1<<j-1;
        for(int j=1;j<=n;j++)
            if(f[j]==j)
            sum[i]+=e[st[j]];
    }
    printf("%lld\n",Dp());
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值