昨晚和Yveh合作的成果……
T1
传送门
题意:给一个正整数集合,求集合中各个子集里各元素的总异或
思路:对于一个数x对自己异或的结果,异或偶数次是x,奇数次为0,而且一个集合的非空子集数目为 2n−1 ,而且很明显对于单个元素出现在子集中的次数均为 2n−1 ,相当于每个元素对自己进行了 2n−1−1 次异或运算,异或运算满足交换律和结合律,所以当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⌋!/∏′z′i=′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(i−j))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();
}