T1
有一个排列,他想从这些数中找出一个子集 {Posi}\{Posi\}{Posi} 满足如下条件
1.子集非空;
2.所有对应位置的值异或和为 000;
3.将这些对应位置的数按原顺序并成一个大数,使得这个数模 PPP 恰好为 000。
对于所有数据有 n,P≤50000n,P\leq 50000n,P≤50000,PPP 是素数,排列随机生成。
首先考虑 n≤31n\leq 31n≤31 的部分分怎么做?
DpDpDp 即可。设 fi,j,kf_{i,j,k}fi,j,k 表示前 iii 个异或和为 jjj 和模 PPP 余 kkk 是否存在方案,O(1)O(1)O(1) 转移即可。
否则,考虑到 [1,31][1,31][1,31] 的子集异或和大概是均匀分布的,而且在组成的大数在 mod P\mod PmodP 意义下也是随机分布的,那么我们只用考虑这些值即可,证明略。
本人在考场上的做法比较麻烦,大概是迭代加深搜索枚举子集大小 kkk ,暴力枚举出 k−1k-1k−1 个数,最后一个数是什么可以直接异或得到,即先强制满足条件 1,21,21,2 ,猜了猜这样的方案mod P\mod PmodP 随机分布,期望试 PPP 次就能试出来,检测 10P10P10P 次完全足够了。也可以通过此题。
#include<bits/stdc++.h>
using namespace std;
const int N=50010;
int n,mod,a[N],c[N],tot=0,tar,pos[N];
int sta[N],top;
bool tf[65536];
void dfs(int x,int s,int t,int p){
if(s==tar){
if(t>n || tf[t]) return ;
p=(1ll*p*c[t]+t)%mod;
if(p==0){
printf("Yes\n");
printf("%d\n",top+1);
for(int i=1;i<=top;i++) printf("%d ",sta[i]);
printf("%d\n",pos[t]);
exit(0);
}
tot++;
if(tot==max(mod*10,100000)) {printf("No\n");exit(0);}
return ;
}
tf[a[x]]=true;
if(n-x>=tar+1-s) dfs(x+1,s,t,p);
sta[++top]=x;
dfs(x+1,s+1,t^a[x],(1ll*p*c[a[x]]+a[x])%mod);
top--;
tf[a[x]]=false;
}
int main(){
scanf("%d %d",&n,&mod);tf[0]=true;
for(int i=1;i<=n;i++) scanf("%d",&a[i]),pos[a[i]]=i;
c[0]=1;for(int i=1;i<=n;i++) c[i]=c[i/10]*10;
for(int i=1;i<n;i++) tar=i,dfs(1,0,0,0);
printf("No\n");
}
T2
给 nnn 个只由 010101 组成的长度为 nnn 的向量,每次询问一个向量的集合,你需要回答这个集合是否线性相关(集合中的元素可以出现多次,事实上,当一个元素出现超过 111 次,这个向量集合显然线性相关)
由于无法输入 n∗nn*nn∗n 的表,nnn 个向量给出方式如下:
对于第 iii 个向量(行列都从 111 开始标号),会给出一个集合(集合的所有元素均小于 iii ),如果该集合为空集,那么表示这个向量只有第 iii 维(列)为 111 ,其余均为 000 。否则该向量就是集合中所有元素对应向量的异或和(集合元素可以出现多次,即对应的异或多次)。
对于向量的异或定义为每一维都独立异或后的结果。
n≤5∗104,q≤2∗104,k≤80,∑C≤1.2∗105n\leq 5*10^4,q\leq 2*10^4,k\leq 80,\sum C\leq 1.2*10^5n≤5∗104,q≤2∗104,k≤80,∑C≤1.2∗105
可以使用正确的线性基复杂度做到 O(nqk2w)O(\frac {nqk^2}{w})O(wnqk2) ,通过 50%50\%50% 的测试点。
正解直接考虑给每一个 k=0k=0k=0 的向量随机一个 x∈[0,2100]x\in[0,2^{100}]x∈[0,2100] 值,k≠0k\not=0k=0 的照常异或。
可以看到,原来线性有关的现在必然线性有关,原来线性无关的现在可能线性有关,但概率很小,几乎不可能出现,概率大概有多小我也不会分析,时间复杂度直接变成 O(qk2100w)O(\frac {qk^2100}{w})O(wqk2100) ,可以通过此题。
#include<bits/stdc++.h>
using namespace std;
const int N=50010;
int n,k,q,x;
bitset<100> p[N],t[100],tmp;
int main(){
srand(time(0));
scanf("%d %d",&n,&q);
for(int i=1;i<=n;i++){
scanf("%d",&k);
if(k==0) for(int j=0;j<100;j++) p[i][j]=rand()&1;
else{
p[i].reset();
while(k--) scanf("%d",&x),p[i]=(p[i]^p[x]);
}
}
while(q--){
scanf("%d",&k);
bool we=true;
for(int i=0;i<100;i++) t[i].reset();
while(k--){
scanf("%d",&x);
if(!we) continue;
tmp=p[x];
for(int i=99;i>=0;i--) if(tmp[i]){
if(t[i].any()) tmp=(tmp^t[i]);
else {t[i]=tmp;break;}
}
if(!tmp.any()) we=false;
}
if(we) printf("0");
else printf("1");
}
}
T3
有 nnn 个研究室在做关于鱼的研究,这个 nnn 个鱼塘连成一棵有边权的树,每条鱼每天可以游最多一个单位。
现在研究的需求是形如在第 AiA_iAi 天在 CiC_iCi 鱼塘需要至少有 BiB_iBi 条鱼。
假设鱼都很听话(即想让它们去哪就去哪),问至少需要购买几条
鱼才能满足研究的需求。
考场上想到了有源汇有上下界最小流,但是没能拿到别人同样做法的 505050 分,常数较差的小子只有 303030 分。
网络流做法考虑将每一个限制都看成一个点,如果一条鱼能在满足 xxx 要求下能同时满足 yyy 要求且 Ax<AyA_x<A_yAx<Ay,从 xxx 连一条边到 yyy 权值。
考虑到一个点必须被经过 ≥Bi\geq B_i≥Bi 次,很容易想到拆点并且用下界来限制流量。
流量就相当于一条鱼,每一个点都可以作为一条鱼的开头,也可以作为结尾,故都向源汇连边,跑有源汇有上下界最小流即可。
正解做法太过复杂
首先可以像上面建图转化为求这张 DAGDAGDAG 的最小链覆盖,也就是最长反链。
假设我们现在将所有询问都挂在对应的点上。
考虑对于一个子树来说,询问点的最长反链应该满足什么条件?
1.在任意一个子树中对应的询问点都是反链。
2.存在一个时刻 ttt 满足,反链上的任意一个点都不能在 ttt 时刻到达根节点,而且根节点不能在 ttt 时刻出发到达反链上的任意一个点。
第一条显然
第二条考虑先证明它的逆否命题:对于任意时刻 ttt 满足,要么存在反链上的一个点在 ttt 时刻到达根节点,要么根节点能在 ttt 时刻出发到达反链上的一个点。
由于时间是在实数域上的,所以两种条件对应的时间区间一定是闭区间,一定有交点,所以存在时刻 ttt 同时满足两个条件,可推出不是反链。
所以是反链可以推出条件 222 ,必要性证毕,充分性显然。
那么就可以 DpDpDp 了。怎么 DpDpDp 留坑待填。
本文详细分析了三道算法竞赛题目,涉及排列的子集异或和问题、向量线性相关判断及鱼塘研究的最优化配置。针对第一题,通过动态规划和随机策略解决;第二题利用位运算快速判断线性相关性;第三题转化成长链覆盖问题。文章探讨了如何在竞赛中灵活运用数据结构和算法,以及在实际问题中构建网络流模型。
234

被折叠的 条评论
为什么被折叠?



