BestCoder Round #77 (div.2)解题报告

昨晚和Yveh合作的成果……


T1
传送门
题意:给一个正整数集合,求集合中各个子集里各元素的总异或
思路:对于一个数x对自己异或的结果,异或偶数次是x,奇数次为0,而且一个集合的非空子集数目为 2n1 ,而且很明显对于单个元素出现在子集中的次数均为 2n1 ,相当于每个元素对自己进行了 2n11 次异或运算,异或运算满足交换律和结合律,所以当n=1时原样输出(只出现了1次),n>1的时候(各元素出现奇数次)输出0即可
代码:

#include<cstdio>
using namespace std;
int t,n,a;
main()
{
    scanf("%d",&t);
    while (t--)
    {
        scanf("%d",&n);
        for (int i=1;i<=n;i++) scanf("%d",&a);
        if (n==1) printf("%d\n",a);
        else printf("0\n");
    }
}

T2
传送门
题意:给定一个小写字母组成的字符串且可任意改变顺序,求不同的回文串数量
思路:对于长度为l的字符串,合法情况下,前半段排列一定时后半段是固定的,前半段的排列方法为 l/2! ,并且要除去相同元素,即 l/2!/zi=a((sum[i]/2)!) ,sum[i]指整个字符串中i出现的次数,所以预处理阶乘,求计算时求下逆元即可
代码:

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define mod 1000000007
#define LL long long
using namespace std;
int t,num[1010];
LL fac[1010];
LL qr(LL x,int y)
{
    LL ans=1;
    while (y)
    {
        if (y&1) ans=ans*x%mod;
        y>>=1;
        x=x*x%mod;
    }
    return ans;
}
char s[1010];
main()
{
    fac[0]=1;
    for (int i=1;i<=1000;i++) fac[i]=fac[i-1]*i%mod;
    scanf("%d",&t);
    while (t--)
    {
        memset(num,0,sizeof(num));
        scanf("%s",s);
        int flag=0;
        for (int i=0;i<strlen(s);i++)
        {
            num[s[i]]++;
            if (num[s[i]]&1) flag++;
            else flag--;
        }
        if (flag>1) {printf("0\n");continue;}
        LL ans=fac[strlen(s)/2];
        for (int i='a';i<='z';i++)
        if (num[i]>1)
        ans=ans*qr(fac[num[i]>>1],mod-2)%mod;
        printf("%d\n",ans);
    }
}

T3
传送门
题意:给定一个初始的01矩阵,并有若干次操作使某一位置的0变成1,求在第几次操作后第0行与第n-1行无法联通(从第x行任取一点,不经过1、只走0且不超边界地(包括起点终点)移动到第y行任一点,则说x,y联通)
思路:类似于星球大战的做法,倒着处理,相当于一个个往图里加边使其联通,不过注意的是第0行和第n-1行是分别联通的(你可以想成边界之外没有山,都是平原,随便走),所以初始化时注意下(如果不这么做,那么每次判断图的连通性时需要各个枚举, O(m2) 伤不起)
代码:

#include<cstdio>
#include<cstring>
#include<iostream>
using namespace std;
int n,m,t,q,tot;
int first[250010],qx[250010],qy[250010],father[250010];
char s[510][510];
typedef pair<int,int> xy;
struct edge
{
    xy u,v;//这里闲的蛋疼,写点对
    int next;
}e[1000010];
void add(int x1,int y1,int x2,int y2)
{
    e[++tot].u.first=x1;
    e[tot].u.second=y1;
    e[tot].v.first=x2;
    e[tot].v.second=y2;
    e[tot].next=first[x1*m+y1];
    first[x1*m+y1]=tot;
}
int find(int x)
{
    if (father[x]!=x) father[x]=find(father[x]);
    return father[x];
}
void unions(int x,int y)
{
    x=find(x);
    y=find(y);
    if (x==y) return;
    father[x]=y;
}
main()
{
    scanf("%d",&t);
    while (t--)
    {
        scanf("%d%d",&n,&m);
        for (int i=0;i<n;i++) scanf("%s",s[i]);
        tot=0;
        memset(first,0,sizeof(first));
        for (int i=0;i<n;i++)
        for (int j=0;j<m;j++)
        {
            if (!i) father[j]=0;
            else if (i==n-1) father[i*m+j]=i*m+m-1;
            else father[i*m+j]=i*m+j;
            if (s[i][j]=='1') continue;
            if (j<m-1&&s[i][j+1]=='0') add(i,j,i,j+1),add(i,j+1,i,j);
            if (i<n-1&&s[i+1][j]=='0') add(i,j,i+1,j),add(i+1,j,i,j);
        }
        scanf("%d",&q);
        for (int i=1;i<=q;i++)
        scanf("%d%d",&qx[i],&qy[i]),
        s[qx[i]][qy[i]]='1';
        for (int i=0;i<n;i++)
        for (int j=0;j<m;j++)
        if (s[i][j]=='0')
        {
            for (int k=first[i*m+j];k;k=e[k].next)
            if (s[e[k].v.first][e[k].v.second]=='0') unions(i*m+j,e[k].v.first*m+e[k].v.second);
        }
        if (find(0)==find(m*n-1)) {printf("-1\n");continue;}
        while (q)
        {
            s[qx[q]][qy[q]]='0';
            for (int i=first[qx[q]*m+qy[q]];i;i=e[i].next)
            if (s[e[i].v.first][e[i].v.second]=='0') unions(qx[q]*m+qy[q],e[i].v.first*m+e[i].v.second);
            if (find(0)==find(m*n-1)) break;
            q--;
        }
        printf("%d\n",q);
    }
}

T4
传送门
题意:在一条直线上,给定一些点,使它们分别占据一定的长度,求这些长度乘积的最大和(重叠部分只算一次)
思路:DP,但是我当时写的是 O(n2m) 的,当然是T了,最后看A的人,发现只用记录最远延伸到的点作为状态就可以了,而且利用类似前缀和处理的方法,如果两个点i,j前缀和相差为1,那么它们之间一定存在一个炸弹,i-j就是它的占据长度,所以方程为
f[i]=max(f[i],f[j]+log2(ij))j<i ,最后算的是对数和,所以通过 log(nm)=log(n)+log(m)(n,m>0) 计算,不然会类型溢出,复杂度 O(n2)
代码:

#include<cstdio>
#include<cstring>
#include<cmath>
#include<iostream>
using namespace std;
int t,n,m,a[2010],sum[2010];
bool flag[2010];
double f[2010];
void work()
{
    scanf("%d%d",&n,&m);
    memset(flag,0,sizeof(flag));
    memset(f,0,sizeof(f));
    for (int i=1;i<=m;i++)
    scanf("%d",&a[i]),
    flag[++a[i]]=1;
    for (int i=1;i<=n;i++)
    sum[i]=sum[i-1]+flag[i];    
    for (int i=1;i<=n;i++)
    for (int j=0;j<i;j++)
    if (sum[i]-sum[j]==1)
    f[i]=max(f[i],f[j]+log2(i-j));   
    printf("%d\n",(int)floor(1000000.0*f[n]));
}
main()
{
    scanf("%d",&t);
    while (t--) work();
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值