【模拟赛】8.24模拟赛题解(由ljm同学的博客转载)

Day8题解

本次题目来自:agc005carc080fbzoj3319

T1:豪迈

题意:给定 N N 以及一个长度为N的数列 an a n ,求是否有一棵树满足第 i i 个点到树上最远的点的距离是ai

题解:显然距离最远的两个点是树的直径上的两个端点,那么显然如果最大值的数量小于两个直接输出Impossible。

接着构造出直径,对直径长度分奇偶考虑(设直径长度为 len l e n ):

首先有一个结论:如果存在 ai<len2 a i < l e n 2 ,那么显然不存在这样的一棵树,因为如果 ai<len2 a i < l e n 2 ,那么显然它到另外一个端点的距离更大,不符合题意。

如果len是奇数,那么说明最小值( len2 l e n 2 )的点有两个,然后从最小值枚举到最大值,检查是否存在至少两个值,如果某个值不足两个,那么说明不存在(你连直径都构造不出来)。

偶数同理,不过最小值的点只有1个。

#include<bits/stdc++.h>
#define maxn 100005
using namespace std;
typedef long long LL;
int read()
{
    char c=getchar();int f=1,sum=0;
    while(c<'0' || c>'9'){if(c=='-') f=-1;c=getchar();}
    while(c>='0' && c<='9'){sum=sum*10+c-'0';c=getchar();}
    return sum*f;
}
int T;
int n,a[maxn],cnt[maxn],d;
int main()
{
    T=read();
    while(T--)
    {
        memset(cnt,0,sizeof(cnt));d=0;
        n=read();
        for(int i=1;i<=n;i++)
        a[i]=read(),cnt[a[i]]++,d=max(d,a[i]); 
        if(cnt[d]<2) {puts("Impossible");continue;}
        bool flag=1;
        if(d&1)
        {
            for(int i=d-d/2;i<=d;i++)
            {
                cnt[i]-=2;
                if(cnt[i]<0) {flag=0;break;}
            }
            for(int i=d-d/2;i>=1;i--)
            if(cnt[i]) {flag=0;break;}
        }
        else
        {
            if (!cnt[d/2]) flag=0;
            if(flag)
            {
                cnt[d/2]--;
                for (int i=d/2+1;i<=d;i++)
                {
                    cnt[i]-=2;
                    if (cnt[i]<0){flag=0;break;}
                }
                for(int i=d/2;i>=1;i--)
                if(cnt[i]) {flag=0;break;}
            }
        }
        if(flag) puts("Possible");
        else puts("Impossible");
    }
    return 0;
}
T2:性感ranwen在线翻身

题意:在一条数轴上有无限多的ranwen是趴着的,有 N N 个位置上的ranwen是躺着的,可以通过选择一个奇质数p来翻转一段长度为 p p 的区间,求最少多少次可以把所有ranwen都变成趴着的。

题解:这题我不会,cky太强辣

考虑差分,如果一个位置与前一个位置不同,那么在这个位置设为1,也就是c[i]=s[i]!=s[i1]?1:0,这样一个有效的区间翻转就可以看做消去区间首位的两个1。

由哥德巴赫猜想可以得到以下结论:

1、如果区间长度是奇质数,那么只用翻转一次。

2、如果区间长度是偶数,那么需要翻转两次(大质数-小质数)。

3、如果区间长度是非奇质数,那么需要翻转三次(奇质数和偶数)。

所以我们就要优先找翻转一次的区间,再找翻转两次的区间,最后再找翻转三次的区间。

对奇数位置和偶数位置上的1建立二分图,先匹配长度为奇质数的,在匹配长度为偶数的,最后如果两边各剩一个则答案+3。

代码:

#include<bits/stdc++.h>
#define maxn 10000005
using namespace std;
typedef long long LL;
int read()
{
    char c=getchar();int f=1,sum=0;
    while(c<'0' || c>'9'){if(c=='-') f=-1;c=getchar();}
    while(c>='0' && c<='9'){sum=sum*10+c-'0';c=getchar();}
    return sum*f;
}
int n,pos[maxn],prime[maxn],match[2005],tot;
bool vis1[2005],vis[maxn];
int a[1005],b[1005],cnt1,cnt2,ans;
vector<int> edge[1005];
bool dfs(int x)
{
    for(int i=0;i<edge[x].size();i++)
    if(!vis[edge[x][i]])
    {
        vis[edge[x][i]]=1;
        if(!match[edge[x][i]] || dfs(match[edge[x][i]]))
        {
            match[edge[x][i]]=x;
            return true;
        }
    }
    return false;
}
void shai()
{
    for(int i=2;i<=1e7;i++)
    {
        if(!vis1[i]) prime[++tot]=i;
        for(int j=1;j<=tot && i*prime[j]<=1e7;j++)
        {
            vis1[i*prime[j]]=1;
            if(i%prime[j]==0) break;
        }
    }
    vis1[1]=1;
}
int main()
{
    shai();
    n=read();
    for(int i=1;i<=n;i++)
    {
        int x=read();
        pos[x]=1;
    }
    for(int i=1;i<=1e7+1;i++)
    if(pos[i]!=pos[i-1])
    {
        if(i&1)
        a[++cnt1]=i;
        else b[++cnt2]=i;
    }
    for(int i=1;i<=cnt1;i++)
    for(int j=1;j<=cnt2;j++)
    if(!vis1[abs(a[i]-b[j])])
    edge[i].push_back(j);
    for(int i=1;i<=cnt1;i++)
    {
        memset(vis,0,sizeof(vis));
        ans+=dfs(i);
    }
    printf("%d\n",ans+(cnt1-ans)/2*2+(cnt2-ans)/2*2+((cnt1-ans)&1)*3);
    return 0;
}
ranwen的服务器

题意:给定一棵树,有两种操作:

​ 1 x,询问x到根节点路径上第一个遇到的黑边。

​ 2 x y,将x到y这条路径上所有边染黑。

题解:显然树剖会TLE,而且还难写,所以我们需要转换思路。

考虑使用并查集,用倒着加边的方式来处理所有操作。

但这样做有个问题:因为是倒序的,所以有些边可能很早就被染黑了,但是在倒序处理是把它的染色时间推迟了。所以我们还需要正序来一遍来处理所有黑边被染黑的时间。

都使用并查集即可,正序处理时将黑边合并,用暴力lca处理,因为并查集维护所有黑边集合中深度最小的点,而lca时是跳并查集中的fa,因此每条边最多经过1次。

代码:

#include<bits/stdc++.h>
#define maxn 1000005
#define inf (1<<30)
#define mod 1000000007
using namespace std;
typedef long long LL;
int read()
{
    char c=getchar();int f=1,sum=0;
    while(c<'0' || c>'9'){if(c=='-') f=-1;c=getchar();}
    while(c>='0' && c<='9'){sum=sum*10+c-'0';c=getchar();}
    return sum*f;
}
int n,m;
int head[maxn],to[maxn<<1],nex[maxn<<1],id[maxn<<1],cnt;
int deep[maxn],anc[maxn],bian[maxn];
int fa[maxn],tim[maxn],t[maxn];
int ans[maxn],tot;
struct node{
    int opt,u,v;
}edge[maxn],q[maxn];
void add(int u,int v,int i)
{
    to[++cnt]=v;nex[cnt]=head[u];id[cnt]=i;head[u]=cnt;
}
void dfs(int x,int la)
{
    deep[x]=deep[la]+1;
    for(int i=head[x];i;i=nex[i])
    {
        if(to[i]==la) continue;
        edge[id[i]].u=x;edge[id[i]].v=to[i];
        anc[to[i]]=x;
        dfs(to[i],x);
        bian[to[i]]=id[i];
    }
}
void init(){for(int i=1;i<=n;i++) fa[i]=i;}
int getfa(int x){return fa[x]==x?x:fa[x]=getfa(fa[x]);}
void modify(int u,int v,int ti)
{
    u=getfa(u);v=getfa(v);
    while(u!=v)
    {
        if(deep[u]<deep[v]) u^=v^=u^=v;
        if(tim[u]==0) fa[u]=fa[anc[u]],tim[u]=ti;
        u=fa[u];
    }
}
void add_edge(int u,int v,int ti)
{
    u=getfa(u);v=getfa(v);
    while(u!=v)
    {
        if(deep[u]<deep[v]) u^=v^=u^=v;
        if(tim[u]==ti) fa[u]=fa[anc[u]];
        u=anc[u];
    }
}
int main()
{
    n=read();m=read();
    for(int i=1;i<n;i++)
    {
        int u=read(),v=read();
        add(u,v,i);add(v,u,i);
    }
    dfs(1,0);init();
    for(int i=1;i<=m;i++)
    {
        q[i].opt=read();q[i].u=read();
        if(q[i].opt==2)
        {
            q[i].v=read();
            modify(q[i].u,q[i].v,i);
        }
    }
    init();
    for(int i=2;i<=n;i++)
    if(!tim[i])
    {
        int u=getfa(edge[bian[i]].u),v=getfa(edge[bian[i]].v);
        fa[v]=u;
    }
    for(int i=m;i>=1;i--)
    {
        if(q[i].opt==1)
        ans[++tot]=bian[getfa(q[i].u)];
        else add_edge(q[i].u,q[i].v,i);
    }
    for(int i=tot;i>=1;i--)
    printf("%d\n",ans[i]);
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值