T1 Set
【题目描述】
你手上有 N 个非负整数, 你需要在这些数中找出一个非空子集, 使得它的元素之和能被N 整除. 如果有多组合法方案, 输出任意一组即可.
注意: 请使用高效的输入输出方式避免输入输出耗时过大.
【输入格式】
第一行一个整数 N, 代表数字个数.
接下来一行 N 个数, 代表你手上的数.
【输出格式】
如果无解, 输出-1.
否则, 第一行输出一个整数 M, 代表你选择的数的个数.
接下来一行 M 个数, 代表你选中元素的下标, 这 M 个数必须两两不同.
解析
没有想到前缀和,想到了就是一道大水题。(雾
应该人人都会想到在输入时对一个数取模后再存到桶里面吧。但是这样只能搜索,T飞。加上我的迭代加深都只有70分。
显然这道题是不会出现无解的情况的,可以考虑前缀和,把输入的数加在前缀和里面,对 n n n取模后再存到桶里面。因为有 n n n个数,而前缀和只有 n − 1 n-1 n−1种情况,所以必定会出现两个前缀和 i i i和 j j j,使 s u m [ i ] = = s u m [ j ] sum[i]==sum[j] sum[i]==sum[j]。根据模数的定义, s u m [ i ] − s u m [ j ] = = 0 sum[i]-sum[j]==0 sum[i]−sum[j]==0,即 i + 1 i+1 i+1 ~ j j j中的数的和模 n n n等于0,符合题意,所以直接输出 i + 1 i+1 i+1 ~ j j j就好了。
虐我智商。
代码
70分迭代加深
#include<cstdio>
#include<algorithm>
#define ll long long
#define maxn 1000005
using namespace std;
struct node
{
int v,next;
}tr[maxn];
int n,fl;
int tot,head[maxn],cnt[maxn],ans[maxn];
ll sum[maxn];//前缀和
ll read()
{
ll ret=0,fl=1;
char ch;
while((ch=getchar())<'0'||ch>'9')
{
if(ch=='-')
fl=-1;
}
ret=ch-'0';
while((ch=getchar())>='0'&&ch<='9')
ret=(ret<<3)+(ret<<1)+ch-'0';
return ret*fl;
}
void print(ll x)
{
if(x<0)
{
putchar('-');
x=-x;
}
if(x>9)
print(x/10);
putchar(x%10+'0');
}
void printans(int x)
{
print(x);
putchar('\n');
for(int i=1;i<=x;++i)
{
print(ans[i]);
putchar(' ');
}
}
ll gcd(int x,int y)
{
return (!y)?x:gcd(y,x%y);
}
void add(int x,int y)
{
tot++;
tr[tot].v=y;
tr[tot].next=head[x];
head[x]=tot;
cnt[x]++;//记录模数个数
}
void dfs(int all,int lst,ll ret)//总共搜索的数的个数,上一次搜索的模数,还剩的数
{
if(ret==0)
{
fl=1;
printans(all);
return;//搜完了
}
if(sum[lst]<ret)return;//剪枝
int nxt=lst;
while((!cnt[nxt]||nxt>ret)&&nxt>0)nxt--;
if(nxt==0)return;
int mx=min(ret/nxt,(ll)cnt[nxt]),allnxt=all+1;
for(int t=head[nxt];t&&allnxt<=all+mx;t=tr[t].next)
{
int y=tr[t].v;
ans[allnxt]=y;
allnxt++;
}//提前记录答案
for(int i=mx;i>=1;--i)
{
allnxt--;
dfs(allnxt,nxt-1,ret-i*nxt);
}
}
int main()
{
freopen("set.in","r",stdin);
freopen("set.out","w",stdout);
n=read();
for(int i=1;i<=n;++i)
{
ll x=read();
add(x%n,i);
}
if(cnt[0])
{
int ans=tr[head[0]].v;
print(1);
putchar('\n');
print(ans);
return 0;
}//特判模数为0
for(int i=1;i<n;++i)
{
int x=gcd(n,i),y;
y=n/x;
if(cnt[i]>=y)
{
print(y);
putchar('\n');
int nw=1;
for(int t=head[i];t&&nw<=y;t=tr[t].next)
{
int z=tr[t].v;
print(z);
putchar(' ');
nw++;
}
fl=1;
}
if(fl)
return 0;
}//特判倍数
for(int i=0;i<n;++i)
sum[i]=sum[i-1]+cnt[i]*i;//计算模数前缀和
for(int i=1;i<=n;++i)//枚举搜索的倍数,迭代加深
{
fl=0;
dfs(0,n-1,n*i);
if(fl)break;
}
return 0;
}
正解
#include<cstdio>
#define ll long long
#define maxn 100005
using namespace std;
ll n,fl;
ll sum[maxn],tong[maxn];
void print(ll x,ll y)
{
printf("%lld\n",y-x);
for(int i=x+1;i<=y;++i)
printf("%lld ",i);
}
int main()
{
freopen("set.in","r",stdin);
freopen("set.out","w",stdout);
scanf("%lld",&n);
for(int i=1;i<=n;++i)
{
ll x;
scanf("%lld",&x);
if(x%n==0)
{
printf("1\n%lld",i);
fl=1;
}
sum[i]=(sum[i-1]+x)%n;
}
if(fl)return 0;
for(int i=1;i<=n;++i)
{
if(tong[sum[i]])
{
print(tong[sum[i]],i);
break;
}
else tong[sum[i]]=i;
}
return 0;
}
T2 Read
【题目描述】
热爱看书的你有 N 本书, 第 i 本书的种类为 A[i]. 你希望每天能够看一本书, 但是不希望
连续两天看种类相同的书. 为了达成这个条件, 你需要选择一些书不看, 作为一个好学生, 你
希望不看的书尽可能少, 求最少可以有多少书不看.
【输入格式】
为了避免输入文件过大, 我们采取如下方式生成 A[i].
第一行读入两个个整数 M, K.
接下来一行读入 M 个整数 count[i], 其中𝑁 = ∑ 𝑐𝑜𝑢𝑛𝑡[𝑖].
接下来一行读入 M 个整数 X[i].
接下来一行读入 M 个整数 Y[i].
接下来一行读入 M 个整数 Z[i].
然后按照下面这段代码生成.
int N = 0, S = (1 << K) - 1;
for (int i = 1; i <= M; ++i)
{
N = N + 1;
A[N] = X[i];
long long last = X[i];
for (int j = 1; j < count[i]; ++j)
{
last = (last * Y[i] + Z[i]) & S;
N = N + 1;
A[N] = last;
}
}
注意:因为生成 A[i]的方法不好用语言描述, 所以用代码来表达. 直接照搬代码的话后果自负.
【输出格式】
输出一个整数表示答案.
解析
一道我想了一个多小时的水题。
若贪心地想,为了避免相邻种类重复,对于同一个种类,明显应该各一个放一个。所以,若一个种类的个数小于总数的一半,那么这些数一定不会计入答案,若有一个种类的数超过了总数的一半,那么这个种类一定会有一些数计入答案,那么其它种类的个数一定小于总数的一半,一定不会计入答案。
所以我们可以记录众数,看众数的个数有没有大于 n 2 \frac{n}{2} 2n。
如何统计众数?空间限制只有16M,最多只能开400万的数组。
考虑以前用过的一种方法,设 l s t lst lst表示当前的众数, t o t tot tot表示当前众数的个数,遇到与 l s t lst lst相等的数, t o t tot tot加1,否则 t o t tot tot减1,当 t o t tot tot等于0时更新 l s t lst lst为当前数,同时 t o t tot tot赋值为1.举个例子,设当数列为:
2,1,1,2,1,3,1,1,4
跑下来的 t o t tot tot和 l s t lst lst分别为:
lst: 2, 1, 1, 1, 1, 1, 1, 1, 1
tot:1, 1, 2, 1, 2, 1, 2, 3, 2
最后求出来的众数就是 1 1 1。
但是这个方法只对众数个数大于 n 2 \frac{n}{2} 2n有用,当众数不大于 n 2 \frac{n}{2} 2n时,此方法不再适用,例如:
1,1,2,2,3,3,1
跑下来的 t o t tot tot和 l s t lst lst分别为:
lst: 1, 1, 1, 2, 2, 2, 3, 3, 3
tot: 1, 2, 1, 1, 2, 1, 1, 2, 1
这样就出现了问题。
怎么办呢?因为这道题只是求区间众数是否大于 n 2 \frac{n}{2} 2n,所以我们还可以再扫一遍。当我们求出 l s t lst lst后,再扫一遍,统计 l s t lst lst的个数,若 l s t lst lst小于 n 2 \frac{n}{2} 2n,那么所有的数都可以选,否则统计答案为 s u m − ( n − s u m + 1 ) sum-(n-sum+1) sum−(n−sum+1)。
代码
#include<cstdio>
#define ll long long
#define maxn 1005
using namespace std;
ll n,m,k,mod,tot,lst,su;
ll cnt[maxn],x[maxn],y[maxn],z[maxn];
int main()
{
freopen("read.in","r",stdin);
freopen("read.out","w",stdout);
scanf("%lld%lld",&m,&k);
mod=(1<<k)-1;
for(int i=1;i<=m;++i)
{
scanf("%lld",&cnt[i]);
n+=cnt[i];
}
for(int i=1;i<=m;++i)
scanf("%lld",&x[i]);
for(int i=1;i<=m;++i)
scanf("%lld",&y[i]);
for(int i=1;i<=m;++i)
scanf("%lld",&z[i]);
for(int i=1;i<=m;++i)
{
ll nw=x[i];
if(tot==0)
{
lst=nw;
tot++;
}
else if(nw!=lst)
tot--;
else if(nw==lst)
tot++;
for(int j=1;j<cnt[i];++j)
{
nw=(nw*y[i]+z[i])&mod;
if(tot==0)
{
lst=nw;
tot++;
}
else if(nw!=lst)
tot--;
else if(nw==lst)
tot++;
}
}
for(int i=1;i<=m;++i)
{
ll nw=x[i];
if(nw==lst)su++;
for(int j=1;j<cnt[i];++j)
{
nw=(nw*y[i]+z[i])&mod;
if(nw==lst)su++;
}
}
if(su<=(n+1)/2)printf("0");
else printf("%lld",su-(n-su+1));
return 0;
}
T3 Race
【题目描述】
一年一度的运动会开始了. 有 N 个选手参赛, 第 i 个选手有一个能力值 A[i](保证 A[i]两两不同), 比赛一共进行了
2
m
2^m
2m天. 在第 j 天(0 ⩽ 𝑗 ⩽
2
m
−
1
2^m − 1
2m−1)的比赛中, 第 i 个选手的得分为 A[i] xor j, 然后从大到小排名, 排名为 x(x 从 0 开始)的同学会获得𝑥2的积分, 你需要求出每个同学最后总的积分和 q[i]模
1
0
9
+
7
10^9 + 7
109+7的结果 p[i]. 为了避免输出文件过大, 你只要输出 p[i]的异或和即可.
【输入格式】
第一行三个整数, 分别代表 N, M.
接下来一行 N 个整数, 第 i 个数代表 A[i].
【输出格式】
一个整数代表答案.
【数据范围】
对于 10%的数据, M <= 5.
对于 30%的数据, N <= 100.
对于 50%的数据, N <= 1000.
对于 100%的数据, N <= 200000, M <= 30, A[i] <
2
m
2^m
2m.
解析
一道思维好题。
本题的突破口在于m的范围,m最大为30,而 2 30 = 1073741824 2^{30}=1073741824 230=1073741824,显然枚举天数是肯定会 T L E TLE TLE的。
再看j的范围 0 ≤ j ≤ 2 m − 1 0\leq j\leq 2^m-1 0≤j≤2m−1,即在二进制数下 0 ≤ j ≤ 111 ⋯ 111 0\leq j\leq 111\cdots111 0≤j≤111⋯111,即对于二进制数下前m位,每一位都可以取0或1.又因为这道题是异或,所以 A [ i ] A[i] A[i]的每一位都可以异或0或者异或1.
再来考虑异或的性质。若两个数前k-1位已经相同了,i的第k位是0,j的第k位是1.那么当两个数异或1时,i大于j,反之i小于j。这对答案的统计十分重要。
那么如何保证前k位完全相同呢?因为二进制数下每个数位只能是0或者1,所以可以把 A A A数组存进 T r i e Trie Trie树里面,记录经过每个节点的数的个数。
void insert(ll x)
{
int p=0;
for(int i=m-1;i>=0;--i)
{
int y=(x>>i)&1;
if(!tri[p][y])tri[p][y]=++cnt;//动态开点
size[tri[p][y]]++;
p=tri[p][y];
}
}
然后从根节点开始搜索,设状态为 d f s ( p , s u m , n u m ) dfs(p,sum,num) dfs(p,sum,num),表示当前遍历到 T r i e Trie Trie树中的节点 p p p,当前的答案 a n s ans ans,比当前节点小的数的个数 n u m num num,当 t r i [ p ] [ 0 ] tri[p][0] tri[p][0]不为0时,说明这一位为0的数的个数不为0.那么当当前异或的值是1时, t r i [ p ] [ 1 ] tri[p][1] tri[p][1]中的所有数都比 t r i [ p ] [ 0 ] tri[p][0] tri[p][0]中的所有数小,这时统计答案。又因为有 2 m − 1 2^{m-1} 2m−1时当前异或的值为1,所以答案还要乘以 2 m − 1 2^{m-1} 2m−1,记录 n u m num num,再往下搜索:
int x=tri[p][0],y=tri[p][1];
if(x)
dfs(x,(sum+size[y]*size[y]%mod*(1<<(m-1))%mod+size[y]*num%mod*(1<<(m-1))%mod)%mod,num+size[y]);//有一定的概率0^1=1
当 t r i [ p ] [ 1 ] tri[p][1] tri[p][1]不为0时同理:
if(y)
dfs(y,(sum+size[x]*size[x]%mod*(1<<(m-1))%mod+size[x]*num%mod*(1<<(m-1))%mod)%mod,num+size[x]);
最后,若 t r i [ p ] [ 0 ] tri[p][0] tri[p][0]和 t r i [ p ] [ 1 ] tri[p][1] tri[p][1]都为0时,说明已经到底了,这时统计最终结果,即 a n s = a n s x o r s u m ans =ans\ xor\ sum ans=ans xor sum,回溯。
代码
#include<cstdio>
#define ll long long
#define maxn 50000005
#define mod 1000000007
using namespace std;
int n,m,cnt;
ll ans;
ll size[maxn],tri[maxn][2];
void insert(ll x)
{
int p=0;
for(int i=m-1;i>=0;--i)
{
int y=(x>>i)&1;
if(!tri[p][y])tri[p][y]=++cnt;//动态开点
size[tri[p][y]]++;
p=tri[p][y];
}
}
ll dfs(int p,ll sum,ll num)
{
int x=tri[p][0],y=tri[p][1];
if(x)
dfs(x,(sum+size[y]*size[y]%mod*(1<<(m-1))%mod+size[y]*num%mod*(1<<(m-1))%mod)%mod,num+size[y]);//有一定的概率0^1=1
if(y)
dfs(y,(sum+size[x]*size[x]%mod*(1<<(m-1))%mod+size[x]*num%mod*(1<<(m-1))%mod)%mod,num+size[x]);
if(x==0&&y==0)
ans^=sum;
}
int main()
{
freopen("race.in","r",stdin);
freopen("race.out","w",stdout);
scanf("%d%d",&n,&m);
for(int i=1;i<=n;++i)
{
ll x;
scanf("%lld",&x);
insert(x);//加入节点到trie树中
}
dfs(0,0,0);
printf("%lld",ans);
return 0;
}