[37](CSP 集训)CSP-S 模拟 7
A.median
你说得对,但是你知道怎么不排序求中位数吗
inline int mid(int a1,int a2,int a3,int a4,int a5){
int d=-inf,x=inf,cd=-inf,cx=inf;
if(a1>d) cd=d,d=a1;
else if(a1>cd) cd=a1;
if(a1<x) cx=x,x=a1;
else if(a1<cx) cx=a1;
if(a2>d) cd=d,d=a2;
else if(a2>cd) cd=a2;
if(a2<x) cx=x,x=a2;
else if(a2<cx) cx=a2;
if(a3>d) cd=d,d=a3;
else if(a3>cd) cd=a3;
if(a3<x) cx=x,x=a3;
else if(a3<cx) cx=a3;
if(a4>d) cd=d,d=a4;
else if(a4>cd) cd=a4;
if(a4<x) cx=x,x=a4;
else if(a4<cx) cx=a4;
if(a5>d) cd=d,d=a5;
else if(a5>cd) cd=a5;
if(a5<x) cx=x,x=a5;
else if(a5<cx) cx=a5;
return a1+a2+a3+a4+a5-d-cd-x-cx;
}
你说得对,但是你知道怎么不排序求 $n=3$ 中位数吗
return a1+a2+a3-max(a1,a2,a3)-min(a1,a2,a3);
这道题貌似推不了容斥,因为我没做出来
思路是枚举全部中位数,然后从五个序列里面找数填
设 \(f_{i,1/2/3}\) 表示第 \(i\) 个序列里小于/等于/大于当前序列数字的有多少个,然后不难推出美丽的式子
另外,这里的 f 直接靠 lower_bound 就可求,前提是你需要对数组排个序
排序显然是不影响答案的
美丽的式子
ans=(ans+
f[1][2]*f[2][1]*f[3][1]*f[4][3]*f[5][3]+
f[1][2]*f[2][1]*f[3][3]*f[4][1]*f[5][3]+
f[1][2]*f[2][1]*f[3][3]*f[4][3]*f[5][1]+
f[1][2]*f[2][3]*f[3][1]*f[4][1]*f[5][3]+
f[1][2]*f[2][3]*f[3][1]*f[4][3]*f[5][1]+
f[1][2]*f[2][3]*f[3][3]*f[4][1]*f[5][1]+
f[1][1]*f[2][2]*f[3][1]*f[4][3]*f[5][3]+
f[1][1]*f[2][2]*f[3][3]*f[4][1]*f[5][3]+
f[1][1]*f[2][2]*f[3][3]*f[4][3]*f[5][1]+
f[1][3]*f[2][2]*f[3][1]*f[4][1]*f[5][3]+
f[1][3]*f[2][2]*f[3][1]*f[4][3]*f[5][1]+
f[1][3]*f[2][2]*f[3][3]*f[4][1]*f[5][1]+
f[1][1]*f[2][1]*f[3][2]*f[4][3]*f[5][3]+
f[1][1]*f[2][3]*f[3][2]*f[4][1]*f[5][3]+
f[1][1]*f[2][3]*f[3][2]*f[4][3]*f[5][1]+
f[1][3]*f[2][1]*f[3][2]*f[4][1]*f[5][3]+
f[1][3]*f[2][1]*f[3][2]*f[4][3]*f[5][1]+
f[1][3]*f[2][3]*f[3][2]*f[4][1]*f[5][1]+
f[1][1]*f[2][1]*f[3][3]*f[4][2]*f[5][3]+
f[1][1]*f[2][3]*f[3][1]*f[4][2]*f[5][3]+
f[1][1]*f[2][3]*f[3][3]*f[4][2]*f[5][1]+
f[1][3]*f[2][1]*f[3][1]*f[4][2]*f[5][3]+
f[1][3]*f[2][1]*f[3][3]*f[4][2]*f[5][1]+
f[1][3]*f[2][3]*f[3][1]*f[4][2]*f[5][1]+
f[1][1]*f[2][1]*f[3][3]*f[4][3]*f[5][2]+
f[1][1]*f[2][3]*f[3][1]*f[4][3]*f[5][2]+
f[1][1]*f[2][3]*f[3][3]*f[4][1]*f[5][2]+
f[1][3]*f[2][1]*f[3][1]*f[4][3]*f[5][2]+
f[1][3]*f[2][1]*f[3][3]*f[4][1]*f[5][2]+
f[1][3]*f[2][3]*f[3][1]*f[4][1]*f[5][2]
)%p;
实际上这里式子没推完,只是一小部分:2*小于中位数+中位数+2*大于中位数
此外还有 小于中位数+2*中位数+2*大于中位数 等等情况
你要是有毅力的话可以推一下
但实际上写个深搜更有性价比
UPD: lbtl 赛时把这一坨玩意全推出来了,lbtl 比较有毅力
#include<bits/stdc++.h>
using namespace std;
#define int long long
int n;
const int p=998244353;
int a[6][100001];
int ans=0;
int f[6][40];
map<int,bool>mp;
int dfs(int now,int les,int equ,int gre){
if(now>5) return 1;
int res=0;
if(les<2 and f[now][1]) res=(res+dfs(now+1,les+1,equ,gre)*f[now][1])%p;
if(f[now][2]) res=(res+dfs(now+1,les,equ+1,gre)*f[now][2])%p;
if(gre<2 and f[now][3]) res=(res+dfs(now+1,les,equ,gre+1)*f[now][3])%p;
return res;
}
signed main(){
freopen("median.in","r",stdin);
freopen("median.out","w",stdout);
scanf("%lld",&n);
for(int i=1;i<=5;++i){
for(int j=1;j<=n;++j){
scanf("%lld",&a[i][j]);
}
sort(a[i]+1,a[i]+n+1);
}
for(int i=1;i<=5;++i){
for(int j=1;j<=n;++j){
int mid=a[i][j];
if(mp.count(mid)) continue;
mp[mid]=true;
memset(f,0,sizeof f);
for(int k=1;k<=5;++k){
f[k][1]=upper_bound(a[k]+1,a[k]+n+1,mid)-a[k]-1;
f[k][3]=n-(lower_bound(a[k]+1,a[k]+n+1,mid)-a[k])+1;
f[k][2]=f[k][1]+f[k][3]-n;
f[k][1]-=f[k][2];f[k][3]-=f[k][2];
}
int res=dfs(1,0,0,0);
ans=(ans+mid*res)%p;
}
}
cout<<ans%p<<endl;
}
B.travel
差点给我卡没了,因为没判自环
那么观察到,题目这个 \(\lim\rightarrow0\) 当且仅当 \(f\) 有什么周期性的性质,否则最终都会收敛
那么周期性的性质,在图上显然就是环了
这里能实现周期性的有两种环
- 忽略自环后,存在一个二元及以上环
为什么在这里要忽略自环,对于其他环,周期显然是环长,但自环的环长是 \(1\),而如果你找到的 f 是以 \(1\) 为周期的,也就意味着任何时候它都不变,那么显然它会是一个常数(实际上这个常数是 \(1\),只有绕着自环绕 \(k\) 圈这一种情况,因为它不能往回走)
- 存在两个自环可达
但是有办法可以让自环不收敛
即使没有周期,当 \(k\rightarrow+\infty\) 时,\(f\rightarrow+\infty\),那么 \(f\) 也是不收敛的
考虑如果两个自环可达,那么可以选择在其中一个环上绕 \(k_1\) 圈,然后走 \(p\) 条边走到另一个环,然后再绕这个环 \(k_2\) 圈,只需要满足 \(k_1+k_2+p=k\),这里 \(p\) 应当是一个定值,当 \(k\rightarrow+\infty\) 是,可以随意选取 \(k_1,k_2\),方案也是无穷的,故不收敛
#include<bits/stdc++.h>
using namespace std;
int n,m;
vector<int>e[500001];
bool self_loop[500001];
int indegree[500001];
int cnt=0;
bool vis[500001];
queue<int>q;
bool dfs(int now){ //用于判断是否有与当前自环联通的自环
int res=false;
vis[now]=true;
for(int i:e[now]){
if(i!=now and !vis[i]){ //i!=now: 自己和自己联通不符合题意
if(self_loop[i]){//找到了联通的自环
return true;
}
else{
res|=dfs(i);
if(res) return true;
}
}
}
return res;
}
int main(){
freopen("travel.in","r",stdin);
freopen("travel.out","w",stdout);
scanf("%d %d",&n,&m);
for(int i=1;i<=m;++i){
int x,y;
scanf("%d %d",&x,&y);
if(x!=y){
e[x].push_back(y);
indegree[y]++;//存储入度,用于找环
}
else self_loop[x]=true;
}
for(int i=1;i<=n;++i){//判第二种情况
if(indegree[i]==0) q.push(i);
if(self_loop[i]){
memset(vis,0,sizeof vis);
if(dfs(i)){
cout<<"Yes";
return 0;
}
}
}
memset(vis,0,sizeof vis);
while(!q.empty()){//判第一种情况,拓扑排序,如果有剩余点说明存在环
int u=q.front();q.pop();
if(vis[u]) continue;
vis[u]=true;cnt++;
for(int j:e[u]){
if(!vis[j]){
indegree[j]--;
if(indegree[j]==0){
q.push(j);
}
}
}
}
cout<<(cnt!=n?"Yes":"No");
}
C.game
观察到博弈论,考虑瞎jb异或
考虑把输入的 \(a_i\) 都异或在一起
发现当且仅当 ans=0 时为 No
100pts
结论:\(n\) 为偶数且 \(a_i\) 两两配对(相同的数出现次数总为偶数),则后手必赢,否则先手必赢
证明:
首先证明 \(n\) 为偶数且 \(a_i\) 两两配对(相同的数出现次数总为偶数),则后手必赢
观察到后手总是可以通过模仿先手的行为来让局面变回两两配对的情况
举例:设当前局面 a a b b
,先手改为 a-dx1 a+dx2 b+dx3 b
,则后手可以改为 a-dx1+dx2 a+dx2-dx1 b+dx3 b+dx3
,还是相同的
其次证明先手必赢的局面
1. \(n\) 为奇数
\(n=1\) 时显然
其他情况下考虑转化成上述必赢局面
设当前 a b c d e
(递增序列),只需要改为 b b d d
,只需要在 e
变成 0
的过程中,挑出一部分数来使得 a+=b-a
c+=d-c
,只需要保证 \(e-0\gt (b-a)+(d-c)\)
可以画个数轴来证,事实上 \(e-0\gt (b-a)+(d-c)\) 一定成立
数轴
绿色部分一定长于红色部分
2. \(n\) 为偶数,不两两配对
设 a b c d
(递增序列)
将其改为 b b c c
,前面需要花费 \(b-a\),后面能够产生 \(d-c\),当 \(b-a\lt d-c\) 时成立
将其改为 a a d d
,前面能够产生 \(b-a\),后面需要花费 \(d-c\),当 \(b-a\gt d-c\) 时成立
综上,该种情况成立
#include<bits/stdc++.h>
using namespace std;
int t;
int n;
int a[200001];
unsigned long long p[200001];
mt19937_64 rd(time(0));
map<int,unsigned long long>mp;
int main(){
freopen("game.in","r",stdin);
freopen("game.out","w",stdout);
scanf("%d",&t);
while(t--){
scanf("%d",&n);
int flag=0;mp.clear();
for(int i=1;i<=n;++i){
scanf("%d",&a[i]);
if(!mp.count(a[i])) mp[a[i]]=rd();
flag^=mp[a[i]];
}
if(n&1){
cout<<"Yes"<<"\n";
continue;
}
cout<<(flag==0?"No":"Yes")<<"\n";
}
}