2022-6-24 #5 uoj Goodbye Dingyou
瑶瑶啊,你再整天这样莫名其妙下去,没有任何希望!😅
010 uoj#350 新年的XOR
哈哈,大水题。
根据经典结论,\(1\oplus 2\oplus\cdots\oplus x=\begin{cases}x&x\bmod 4=0\\1&x\bmod 4=1\\x+1&x\bmod 4=2\\0&x\bmod 4=3\end{cases}\)
也就是说,我们能且只能构造出 \(1\),模四余零,模四余三的数之间两两异或的结果。
那么,对于模四的每个余数,都可以很容易构造出一组解。
#include<stdio.h>
int T;
long long n;
int main(){
scanf("%d",&T);
while(T--){
scanf("%lld",&n);
if(n%4==0){
if(n==0)
puts("1 3");
else printf("1 %lld\n",n);
}
if(n%4==1){
if(n==1)
puts("1 5");
else printf("2 %lld\n",n^1);
}
if(n%4==2){
if(n==2)
puts("3 5");
else printf("2 %lld\n",n);
}
if(n%4==3)
printf("1 %lld\n",n-1);
}
return 0;
}
011 uoj#351 新年的叶子
所有直径的中点重合,要么同时在一个点上,要么同时在一个边上。
对于直径中点在点上的情况,我们发现直径长度缩短当且仅当直径中点(以其为根)只有不超过一棵子树存在深度取到最大值。
对于直径中点在边上的情况,直径长度缩短以边为根后,两个点中有一个点子树中深度最大值的叶子都被染黑了。
然后随便算概率吧,复杂度 \(O(n)\)。
#include<stdio.h>
#include<vector>
using namespace std;
const int maxn=500005,mod=998244353;
int n,tot,ans,mx,rec,ts,leafs,sum;
int H[maxn],t[maxn],fa[maxn],inv[maxn];
vector<int>v[maxn];
void dfs(int x,int last,int d){
int leaf=1;
for(int i=0;i<v[x].size();i++)
if(v[x][i]!=last)
leaf=0,dfs(v[x][i],x,d+1);
fa[x]=last;
if(d>mx)
mx=d,rec=x,tot=0;
if(leaf&&d==mx)
tot++;
}
int main(){
scanf("%d",&n);
for(int i=1,x,y;i<n;i++)
scanf("%d%d",&x,&y),v[x].push_back(y),v[y].push_back(x);
mx=0,dfs(1,0,0),mx=0,dfs(rec,0,0);
if(mx&1){
int rtx=rec;
for(int i=1;i<=mx/2;i++)
rtx=fa[rtx];
int rty=fa[rtx];
mx=tot=0,dfs(rtx,rty,0);
if(tot)
t[++ts]=tot;
mx=tot=0,dfs(rty,rtx,0);
if(tot)
t[++ts]=tot;
}
else{
int rt=rec;
for(int i=1;i<=mx/2;i++)
rt=fa[rt];
mx/=2;
for(int i=0;i<v[rt].size();i++){
tot=0,dfs(v[rt][i],rt,1);
if(tot)
t[++ts]=tot;
}
}
for(int i=1;i<=n;i++)
leafs+=(v[i].size()==1);
for(int i=1;i<=ts;i++)
sum+=t[i];
for(int i=1;i<=n;i++)
inv[i]=i==1? 1:(mod-1ll*(mod/i)*inv[mod%i]%mod),H[i]=(H[i-1]+1ll*inv[i]*leafs)%mod;
for(int i=1;i<=ts;i++)
ans=(ans+H[sum-t[i]])%mod;
printf("%d\n",(ans-1ll*(ts-1)*H[sum]%mod+mod)%mod);
return 0;
}
012 uoj#352 新年的五维几何
怎么比上一道题还简单。。。
根据 AGC020F 的套路,我们枚举每个数的整数部分,小数部分枚举其大小排列,然后直接判断就好了。。。
复杂度 \(O((r-l)^nn!n^2)\)。
#include<stdio.h>
#include<algorithm>
using namespace std;
const int maxn=6;
int n,ps;
int l[maxn],r[maxn],a[maxn][maxn],p[maxn],P[maxn],x[maxn],R[maxn];
double ans,d;
void dfs(int t){
if(t>ps){
for(int i=1;i<=ps;i++)
P[i]=i;
do{
int flg=1;
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
flg&=(x[i]-x[j]>a[i][j])|(x[i]-x[j]==a[i][j]&&P[R[i]]>=P[R[j]]);
ans+=flg;
}while(next_permutation(P+1,P+1+ps));
return ;
}
for(int i=l[p[t]];i<r[p[t]];i++)
x[p[t]]=i,dfs(t+1);
}
int main(){
scanf("%d",&n),d=1;
for(int i=1;i<=n;i++){
scanf("%d%d",&l[i],&r[i]);
if(l[i]<r[i])
p[++ps]=i,R[i]=ps,d*=ps*(r[i]-l[i]);
else x[i]=l[i];
}
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
scanf("%d",&a[i][j]);
dfs(1);
printf("%.9lf\n",ans/d);
return 0;
}
013 uoj#353 新年的代码
dp 细节为什么编了一万年不会……
根据经典结论,我们将 \(R,G,B\) 看作 \(0,1,2\),变换后的串与原串三进制异或值相等。
按照三进制异或值给序列分成尽可能多的对应段,段间显然没有操作,段内操作数量要么是段长,要么是段长减一。(感性上比较显然)
我们发现 \((i,i+1)\) 型操作一定至少一次,而开头对应的操作要么可以放到操作序列开头,要么可以放到操作序列结尾。(在拓扑图中,如果一个度为 \(1\),它要么可以放到拓扑序开头,要么可以放到结尾)
另外,可以发现操作具有可逆性,所以我们可以把放到结尾看成对 \(t\) 进行操作,于是列出 dp:令 \(f_i\) 表示 \(s_{i+1\cdots n}\) 已经被操作成了 \(t_{i+1\cdots n}\),第一个位置被操作成了一个固定字母,能不能用长度减一次操作完成,\(g_i\) 则是将 \(t\) 向 \(s\) 操作的 dp 数组。
然后直接分讨 dp 即可,复杂度 \(O(n)\)。
#include<stdio.h>
#include<iostream>
#include<map>
using namespace std;
const int maxn=500005;
int n,ans;
int sum[maxn],a[maxn],b[maxn],f[maxn],g[maxn];
string s,t;
map<char,int>mp;
inline int trans(int a,int b,int c){//ab->c_
return a==c||a==b||b!=c;
}
int main(){
scanf("%d",&n),cin>>s>>t,mp['R']=0,mp['G']=1,mp['B']=2;
for(int i=1;i<=n;i++)
a[i]=mp[s[i-1]],b[i]=mp[t[i-1]];
for(int i=1;i<=n;i++){
int j=i,s=0;
while(j<=n){
s=(s+a[j]-b[j]+3)%3;
if(s%3==0)
break;
j++;
}
f[j]=g[j]=1,s=(a[j]-b[j]+3)%3;
for(int k=j-1;k>=i;k--){
int A=(a[k]+s)%3,B=(b[k]-s+3)%3;
if(f[k+1]){
if(trans(B,a[k+1],b[k]))
f[k]=1;
if(trans(a[k],a[k+1],A))
g[k]=1;
}
if(g[k+1]){
if(trans(b[k],b[k+1],B))
f[k]=1;
if(trans(A,b[k+1],a[k]))
g[k]=1;
}
s=(s+a[k]-b[k]+3)%3;
}
ans+=(j-i+1)-(f[i]|g[i]);
i=j;
}
printf("%d\n",ans);
return 0;
}
014 uoj#354 新年的投票
非常有意思的题阿!orz hzr。
Sub1:
很容易发现 \({15\choose 7}=6435\) 是比较符合要求的,我们考虑向它凑。
考虑一个这样的策略:如果看到的 \(1\) 数量比 \(0\) 多就认为自己是 \(1\),否则认为自己是 \(0\),计算异或和然后上报。
除了 \(8\times 1+7\times0\) 的情况,其他情况都是数量占优势的数字对应者的投票作为答案,正确性显然。
代码见 Sub2。
Sub3:
两种情况,显然是全 \(0\) 或全 \(1\) 的情况,或者是其中的一种情况。
考虑这个序列的一个极长 \(0\) 前缀,除了全 \(0\) 情况,其余情况最后一个位置都是 \(1\)。同时,很容易分辨一个数是不是在这个全 \(0\) 前缀。
于是,如果一个人前面所有人都是 \(0\),他就认为自己是 \(1\),并给算出来的异或和投 \(2^i\) 张票。
#include<stdio.h>
int main(){
freopen("vote3.ans","w",stdout);
for(int i=1;i<=15;i++){
for(int j=0;j<(1<<14);j++){
int flg=1;
for(int k=1;k<i;k++)
flg&=((j>>(k-1))&1)==0;
if(flg){
int v=__builtin_parity(j)^1;
printf("%d",((v==1? 1:-1)<<(i-1)));
}
else printf("0");
putchar((j==(1<<14)-1)? '\n':' ');
}
}
return 0;
}
Sub2:
不妨继续猜测错误次数,可以发现 \(\frac{2^{15}}{16}=2048\) 恰好符合要求。
为了模仿 Sub3,我们把 \(15\) 个人分成 \(4\) 组,大小分别是 \(1,2,4,8\),一个组对应的数字是其数字的异或和。
直接采用 Sub3 的做法即可做到 \(\frac{1}{16}\) 的错误率。
注意如果要让自己投 \(0\) 票,就需要让自己的成员一半投正一半投负。(第一组显然不会投 \(0\) 票)
#include<stdio.h>
int main(){
freopen("vote2.ans","w",stdout);
for(int c=1;c<=4;c++){
int l=(1<<(c-1)),r=(1<<c)-1;
for(int i=l;i<=r;i++){
for(int j=0;j<(1<<14);j++){
int flg=1,rec=0;
for(int d=1;d<c;d++){
int dl=(1<<(d-1)),dr=(1<<d)-1,x=0;
for(int k=dl;k<=dr;k++)
x^=(j>>(k-1))&1;
flg&=(x==0);
}
int tot=0,val=1,f=0;
for(int d=1;d<=4;d++){
for(int k=1;k<=(1<<(d-1));k++){
tot++;
if(f==0&&i==tot){
f=1,tot--;
continue;
}
if(d!=c)
val^=(j>>(tot-1))&1;
}
}
int res=(flg==0? (i<=(l+r)/2):val)+48;
putchar(res);
}
putchar('\n');
}
}
return 0;
}
Sub4:
很厉害啊!
对于很多的非传统题,很多时候你都需要对你的策略进行更为严谨地刻画,然后调出最优的参数。
我们考虑上述算法的本质是什么:
一个人的投票策略可以看作一个 \(7\) 元的多项式,
md,调不出来,鸽了。
这里是 64 分的提交记录。