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;
}
posted @ 2024-01-07 14:01  wangyuanbo  阅读(12)  评论(0)    收藏  举报  来源