1月6日考试总结
考炸了,赛时只做出了一道题。
A 过关斩将
做法
这道题就是一个很显然的二维最短路,设 \(dis[i][j]\) 表示到达点 \(i\) 且当前的状态为 \(j\) 的最少代价。其中 \(j=0\) 时表示状态为 \(L\) , \(j=1\) 时表示状态为 \(R\) 。
很显然可以用 \(dijkstra\) 来求解,转移方程为 \(dis[v][v.type] = dis[u][u.type] + w(u.type==v.type)\) 。
也可以为 \(dis[v][u.type] = dis[u][u.type] + w(v.type==M)\)。
还有种情况 \(dis[v][v.type] = dis[u][u.type] + w + x(v.type\ne u.type ~~ and ~~ v.type \ne 2)\)
答案为 \(min(dis[t][0],dis[t][1])\) 。
\(Code\)
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=2e5+50;
int T,cnt;
int n,m,s,t,x;
int head[N];
struct edge{
int to,nxt,w;
}e[N*4];
void add(int u,int v,int f)
{
e[++cnt].to=v;
e[cnt].nxt=head[u];
head[u]=cnt;
e[cnt].w=f;
return;
}
string ss;
bool vis[N][4];
int dis[N][4];
struct node{
int val,pos,type;
bool operator >(const node &x)const
{
return val>x.val;
}
};
void dij(int S)
{
for(int i=1;i<=n;i++){
for(int j=0;j<=2;j++)
{
dis[i][j]=1e18;
vis[i][j]=0;
}
}
if(ss[S]=='L')dis[S][0]=0;
else if(ss[S]=='R')dis[S][1]=0;
else if(ss[S]=='M')dis[S][0]=0,dis[S][1]=0;
priority_queue<node,vector<node>,greater<node> >q;
q.push(node{dis[S][0],S,0});q.push(node{dis[S][1],S,1});
while(!q.empty())
{
node t=q.top();q.pop();
if(vis[t.pos][t.type])continue;
vis[t.pos][t.type]=1;
for(int i=head[t.pos];i;i=e[i].nxt)
{
int v=e[i].to;
int op=-1;
if(ss[v]=='L')op=0;
else if(ss[v]=='R')op=1;
else if(ss[v]=='M')op=2;
if(op==t.type&&dis[v][op]>dis[t.pos][t.type]+e[i].w)
{
dis[v][op]=dis[t.pos][t.type]+e[i].w;
q.push(node{dis[v][op],v,op});
}
else if(op==2&&dis[v][t.type]>dis[t.pos][t.type]+e[i].w)
{
dis[v][t.type]=dis[t.pos][t.type]+e[i].w;
q.push(node{dis[v][t.type],v,t.type});
}
else if(dis[v][op]>dis[t.pos][t.type]+x+e[i].w)
{
dis[v][op]=dis[t.pos][t.type]+e[i].w+x;
q.push(node{dis[v][op],v,op});
}
}
}
return;
}
signed main()
{
scanf("%lld",&T);
while(T--)
{
cnt=0;
scanf("%lld %lld %lld %lld %lld",&n,&m,&s,&t,&x);
for(int i=1;i<=n+50;i++)head[i]=0;
cin>>ss;
ss=" "+ss;
for(int i=1,u,v,w;i<=m;i++)
{
scanf("%lld %lld %lld",&u,&v,&w);
add(u,v,w);add(v,u,w);
}
dij(s);
printf("%lld\n",min(dis[t][0],dis[t][1]));
}
return 0;
}
B 翻转游戏
似乎又是一道奇偶性好题? 可我没想到。(悲)
做法
因为从小到大翻转不好翻,所以考虑从大到小翻转,这样做不会对答案产生影响,因为翻转就相当于区间异或,异或运算满足交换律。
那么就这样,如果当前翻转的区间的长度小于等于剩余 \(1\) 的区间长度那就直接翻转。如果当前翻转的区间长度大于剩余 \(1\) 的区间长度那就以剩余 \(1\) 的区间的右端点左右翻转区间的右端点进行翻转。
显然这样子做不会有问题,每次(除最后一次)区间 \(1\) 的个数都会增加或减少偶数个( \(2^{i} ~~ (i\ge 2)\) ),但最后一次区间 \(1\) 的个数会减少奇数个,所以无解的情况就应该为原来区间 \(1\) 的长度为偶数,那么无论怎样又不可能把这个区间 \(1\) 的个数减少为 \(1\)。
\(Code\)
#include<bits/stdc++.h>
using namespace std;
int T;
int n,k,tot;
int ans[35];
int main()
{
scanf("%d",&T);
while(T--)
{
scanf("%d %d",&n,&k);
if(k==0)
{
printf("YES\n0\n");
continue;
}
if(!(k&1))
{
printf("NO\n");
continue;
}
printf("YES\n");
int x=log2(k),R=k,r=1;tot=0;
for(int i=x;i>=0;i--)
{
if((1<<i)<=R-r+1)
{
ans[++tot]=r;
r+=(1<<i);
}
else
{
int tmp=R;
ans[++tot]=R-(1<<i)+1;
R=r-1;
r=tmp-(1<<i)+1;
}
}
printf("%d\n",tot);
for(int i=tot;i>=1;i--)printf("%d ",ans[i]);
printf("\n");
}
return 0;
}
C 大模法师
以前做过,可是赛上只有一个人做出来且不是用的原来的方法。所以这道题有很多战犯。
貌似只有我只会随机化?
做法
假如两个数 \(a=k_1\times m+b_1,b=k_2\times m+b_2\) 在最终的答案集合中,根据同余的性质可得 \(m|a-b\) ,所以每次随机两个数再做差得到 \(x\) ,枚举 \(x\) 的正整数因子即可。
接下来分析每次 \(check\) 的正确率,两个数都在答案集合的概率为 \(\frac{1}{2}\times \frac{1}{2}=\frac{1}{4}\) ,所以每次失败的概率为 \(1-\frac{1}{4}=\frac{3}{4}\),多随机个几十次就行了或者直接卡时。
\(Code\)
代码实现起来还是有很多细节的,比如每次枚举因子可以随机化,因为已经在之前枚举过了;如果最开始相同的个数就已经超过一半了,那就直接输出 \(100000\) 。还有一个优化:如果一个数不行,那么它的倍数就一定不行,举个栗子如果每个数 \(\mod 3\) 以后还是不行,那么 \(\mod 6,9,...\) 都不行,因为比如之前 \(\mod 3=1\) 的数在\(\mod 6\) 后的结果可能为 \(1(1\mod 6),4(4 \mod 6)\) ,\(\mod 9\) 后的结果可能为 \(1(1\mod 9),4(4\mod 9),7(7\mod 9)\) ,所以就更不可能模数相同的超过一半了。(话说好像只有这个优化就能通过本题了?)
#include<bits/stdc++.h>
using namespace std;
const int N=1e6+50;
int a[N],n,ans,cntt[N],mp[N],minn=9999999,maxn=-9999999;
bool vis[N];
bool check(int x)
{
memset(mp,0,sizeof(mp));
for(int i=1;i<=n;i++)
{
int u=a[i]%x;
mp[u]++;
if(mp[u]>=ceil(1.0*n/2))
return true;
}
return false;
}
void divide(int x)
{
for(int i=1;i*i<=x;i++)
{
if(x%i==0&&(i>ans||x/i>ans))
{
if(vis[i]==0)
{
vis[i]=1;
if(check(i))
ans=max(ans,i);
else
for(int j=i*2;j<=maxn;j+=i)vis[j]=1;
}
if(vis[x/i]==0)
{
vis[x/i]=1;
if(check(x/i))
ans=max(ans,x/i);
else
for(int j=(x/i)*2;j<=maxn;j+=(x/i))vis[j]=1;
}
}
else continue;
}
return;
}
mt19937 rd(1936);
int main()
{
double s=clock();
srand(time(0));
scanf("%d",&n);
for(int i=1;i<=n;i++)scanf("%d",&a[i]),cntt[a[i]]++,minn=min(minn,a[i]),maxn=max(maxn,a[i]);
for(int i=minn;i<=maxn;i++)
{
if(cntt[i]>=ceil(1.0*n/2))
{
printf("100000\n");
return 0;
}
}
if(n<=1000&&maxn<=1000)
{
ans=1;
for(int i=2;i<=maxn;i++)
{
memset(mp,0,sizeof(mp));
for(int j=1;j<=n;j++)
{
mp[a[j]%i]++;
if(mp[a[j]%i]>=ceil(1.0*n/2))
{
ans=max(ans,i);
break;
}
}
}
printf("%d\n",ans);
return 0;
}
ans=1;
vis[1]=1;
while(clock()-s<=1980)
{
int x=0,y=0;
while(a[x]==a[y])
x=rd()*rd()%n+1,y=rd()*rd()%n+1;
int z=abs(a[x]-a[y]);
if(z==1||z==0)continue;
divide(z);
}
printf("%d\n",ans);
return 0;
}
D 彩色的树
赛后觉得唯一一个偏正常的题目。
看到多重 \(\sum\) 求和的式子就应该套路的想到单独拆开计算每个点的贡献。
在 \(dfs\) 的过程中统计即可,计算剩下的连通块,注意根节点所在的连通块即可,不得不说 \(dfs\) 的思想真的很妙呀。
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=2e5+50;
int n,tot;
int color[N],cnt[N],siz[N],Tot[N];
struct node{
int nxt,to;
}e[N*2];
int head[N];
void add(int u,int v)
{
e[++tot].to=v;
e[tot].nxt=head[u];
head[u]=tot;
return;
}
void dfs(int u, int fa)
{
int mark = cnt[color[u]];
siz[u] = 1;
for (int j = head[u]; j != 0; j = e[j].nxt)
{
int v = e[j].to;
if (v == fa) continue;
dfs(v, u);
siz[u] += siz[v];
int t = siz[v] - (cnt[color[u]] - mark);
Tot[color[u]] += (long long)t * (t - 1) / 2;
cnt[color[u]] = mark;
}
cnt[color[u]] = mark + siz[u];
return;
}
signed main()
{
scanf("%lld",&n);
for(int i=1;i<=n;i++)scanf("%lld",&color[i]);
for(int i=1,u,v;i<=n-1;i++)
{
scanf("%lld %lld",&u,&v);
add(u,v);add(v,u);
}
dfs(1,0);
for (int i = 1; i <= n; i++)
Tot[i] += (long long)(siz[1] - cnt[i]) * (siz[1] - cnt[i] - 1) / 2;
long long ans = 0;
for (int i = 1; i <= n; i++)
ans += (long long)siz[1] * (siz[1] - 1) / 2 - Tot[i];
printf("%lld\n",ans);
return 0;
}