2019年7月23日(图论)

最水一波考!!!

貌似又水了。
只拿了些\(sb\)分,其他的就\(jj\)了。最郁闷的是\(T1\),前前后后水了2个多小时都没\(yy\)出来,郁闷啊!!!!

还是先从最简单的讲起:

prob2:树的问题(tree)

题目大意:给定一棵无根树,共\(m\)个询问,每次给出\(a\)\(b\),求树的所有形态中使得\(a\)\(b\)祖先或\(b\)\(a\)祖先的方案数。

\(sb\)题,简直送分,典型的红题。求出\(a\)\(b\)\(lca\),进行判断\(a\)\(b\)是否等于\(lca\),然后分类进行调控,通过二者子树\(size\)算出答案

看代码:

#include<iostream>
#include<cstdio>
#include<vector>
using namespace std;
#define in read()
#define fur(i,a,b) for(int i=a;i<=b;i++)
#define ll long long
#define xx 210000
inline int read()
{
    int x=0,f=1;char ch=getchar();
    for(;!isalnum(ch);ch=getchar()) if(ch=='-') f=-1;
    for(;isalnum(ch);ch=getchar()) x=x*10+ch-'0';
    return x*f;
}
vector<int>e[xx];
struct point{ll sz,top,dep,fa,son;bool vis;}dot[xx];
inline void dfs1(int g)
{
    dot[g].sz=1;
    fur(i,0,(int)e[g].size()-1)
    {
        if(!dot[e[g][i]].vis)
        {
            dot[e[g][i]].vis=true;
            dot[e[g][i]].fa=g;
            dot[e[g][i]].dep=dot[g].dep+1;
            dfs1(e[g][i]);
            dot[g].sz+=dot[e[g][i]].sz;
            if(dot[dot[g].son].sz<dot[e[g][i]].sz) dot[g].son=e[g][i];
        }
    }
}
inline void dfs2(int g,int gg)
{
    dot[g].top=gg;
    if(dot[g].son) dfs2(dot[g].son,gg);
    fur(i,0,(int)e[g].size()-1) if(e[g][i]!=dot[g].son&&e[g][i]!=dot[g].fa) dfs2(e[g][i],e[g][i]);
}
inline int lca(int u,int v)
{
    while(dot[u].top!=dot[v].top)
    {
        if(dot[dot[u].top].dep<dot[dot[v].top].dep) swap(u,v);
        u=dot[dot[u].top].fa;
    }
    if(dot[u].dep<dot[v].dep) return u;
    else return v;
}
inline int llca(int u,int v)
{
    int before;
    while(dot[u].top!=dot[v].top) before=v,v=dot[dot[v].top].fa;
    if(v!=u) return dot[u].son;
    else return dot[before].top;
}
int main()
{
    int n=in,m=in;
    fur(i,1,n-1)
    {
        int x=in,y=in;
        e[x].push_back(y);
        e[y].push_back(x);
    }
    dot[1].vis=true;
    dfs1(1);
    dfs2(1,1);
    fur(i,1,m)
    {
        int x=in,y=in;
        int z=lca(x,y);
        if(z!=x)
        {
            if(z!=y) printf("%lld\n",dot[x].sz+dot[y].sz);
            else printf("%lld\n",n-dot[llca(y,x)].sz+dot[x].sz);
        }
        else printf("%lld\n",n-dot[llca(x,y)].sz+dot[y].sz);
    }
    return 0;
}

prob3:盛宴(party)

题目大意:给定一个连通图,并给定两对定点与定长,删去尽可能多的边使得定点对间最短路不超过定长,输出最多删边。

\(My\) \(solutin\):好吧,我觉得这题简单些,考试的时候二分写萎了,结果没跑过,只水了\(50\)分。原本以为可能会\(T\)飞的,后来发现效果不错。虽然复杂度和正确性完全不对,但数据水到这样都能拿一半分,如果考试时边界暴力一波的话应该就能过了(然而事实是会有一部分\(T\)飞,预计得分\(70\)左右),心累,上代码:

#include<iostream>
#include<cstring>
#include<cstdio>
#include<queue>
#include<vector>
#include<algorithm>
using namespace std;
#define in read()
#define fur(i,a,b) for(int i=a;i<=b;i++)
#define xx 3300
#define inf 0x7f
inline int read()
{
    int x=0,f=1;char ch=getchar();
    for(;!isalnum(ch);ch=getchar()) if(ch=='-') f=-1;
    for(;isalnum(ch);ch=getchar()) x=x*10+ch-'0';
    return x*f;
}
int dis1[xx],dis2[xx],pre1[xx],pre2[xx];
vector<int>e[xx];
bool lt[xx][xx],vis1[xx],vis2[xx],key;
int id[xx][xx],pk[xx];
deque<int>q;
inline void spfa(int s,int *dis,bool *vis,int *pre)
{
    dis[s]=0;
    vis[s]=true;
    q.push_back(s);
    while(!q.empty())
    {
        int g=q.front();
        vis[g]=false;
        q.pop_front();
        fur(i,0,(int)e[g].size()-1)
        {
            if(lt[g][e[g][i]])
            {
                if(dis[e[g][i]]>dis[g]+1)
                {
                    dis[e[g][i]]=dis[g]+1;
                    pre[e[g][i]]=g;
                    if(!vis[e[g][i]])
                    {
                        vis[e[g][i]]=true;
                        if(!q.empty())
                        {
                            if(dis[e[g][i]]<=dis[q.front()]) q.push_front(e[g][i]);
                            else q.push_back(e[g][i]);
                        }
                        q.push_back(e[g][i]);
                    }
                }
            }
        }
    }
}
struct edge{int id,u,v,num;}ee[xx<<1];
inline void init()
{
    memset(dis1,inf,sizeof(dis1));
    memset(dis2,inf,sizeof(dis2));
    memset(pre1,0,sizeof(pre1));
    memset(pre2,0,sizeof(pre2));
    memset(vis1,false,sizeof(vis1));
    memset(vis2,false,sizeof(vis2));
}
inline bool cmp(edge x,edge y){return x.num<y.num;}
inline void handle(int t1,int t2,int all)
{
    while(pre1[t1])
    {
        ee[id[pre1[t1]][t1]].num++;
        t1=pre1[t1];
    }
    while(pre2[t2])
    {
        ee[id[pre2[t2]][t2]].num++;
        t2=pre2[t2];
    }
    sort(ee+1,ee+all+1,cmp);
}
inline bool check(int now,int s1,int s2,int t1,int t2,int l1,int l2)
{
    fur(i,1,now)
    {
        lt[ee[i].u][ee[i].v]=false;
        lt[ee[i].v][ee[i].u]=false;
    }
    init();
    spfa(s1,dis1,vis1,pre1);
    spfa(s2,dis2,vis2,pre2);
    fur(i,1,now)
    {
        lt[ee[i].u][ee[i].v]=true;
        lt[ee[i].v][ee[i].u]=true;
    }
    if(dis1[t1]>l1||dis2[t2]>l2) return false;
    else return true;
}
inline bool checked(int *u,int now,int s1,int s2,int t1,int t2,int l1,int l2)
{
    fur(i,1,now)
    {
        lt[ee[u[i]].u][ee[u[i]].v]=false;
        lt[ee[u[i]].v][ee[u[i]].u]=false;
    }
    init();
    spfa(s1,dis1,vis1,pre1);
    spfa(s2,dis2,vis2,pre2);
    fur(i,1,now)
    {
        lt[ee[u[i]].u][ee[u[i]].v]=true;
        lt[ee[u[i]].v][ee[u[i]].u]=true;
    }
    if(dis1[t1]>l1||dis2[t2]>l2) return false;
    else return true;
}
inline void ck(int m,int now,int here,int need,int s1,int s2,int t1,int t2,int l1,int l2)
{
    if(key) return;
    if(now==need)
    {
        if(checked(pk,now,s1,s2,t1,t2,l1,l2)) key=true;
        return;
    }
    fur(i,here,m)
    {
        pk[now+1]=i;
        if(now+1+m-i<need) return;
        ck(m,now+1,here+1,need,s1,s2,t1,t2,l1,l2);
    }
}
int main()
{
    int n=in,m=in,all=0;
    fur(i,1,m)
    {
        int x=in,y=in;
        e[x].push_back(y);
        e[y].push_back(x);
        lt[x][y]=true;
        lt[y][x]=true;
        id[y][x]=id[x][y]=++all;
        ee[all].u=x;
        ee[all].v=y;
        ee[all].id=all;
        ee[all].num=0;
    }
    int s1=in,t1=in,l1=in;
    int s2=in,t2=in,l2=in;
    init();
    spfa(s1,dis1,vis1,pre1);
    spfa(s2,dis2,vis2,pre2);
    if(dis1[t1]>l1||dis2[t2]>l2)
    {
        printf("That's all trouble!\n");
        return 0;
    }
    handle(t1,t2,all);
    int ans=0,L=0,R=m;
    while(L<R)
    {
        int mid=(L+R)>>1;
        if(check(mid,s1,s2,t1,t2,l1,l2)) ans=mid,L=mid+1;
        else R=mid-1;
    }
    ck(m,0,1,ans+1,s1,s2,t1,t2,l1,l2);
    if(key) ans++;
    printf("%d\n",ans);
    return 0;
}

正解:先\(orz\)学长一波

思想很巧妙,求出给定两点对间的路径\(dis(s1,t1)\)与$
dis(s2,t2)\(,设两路间有重合部分\)p1\(与\)p2\(,则我们需要求的就是\)dis(s1,t1)
+dis(s2,t2)-dis(p1,p2)$的最小值(因为边权都为\(1\),所以用总边数减上述
表达式就能得到删边的数目,使其最大则使表达式最小),这样题目就简单很多了

上代码:

#include<iostream>
#include<cstdio>
#include<vector>
#include<queue>
#include<cstring>
using namespace std;
#define in read()
#define fur(i,a,b) for(int i=a;i<=b;++i)
#define xx 3100
inline int read()
{
    int x=0,f=1;char ch=getchar();
    for(;!isalnum(ch);ch=getchar()) if(ch=='-') f=-1;
    for(;isalnum(ch);ch=getchar()) x=x*10+ch-'0';
    return x*f;
}
int dis[xx][xx],vis[xx];
queue<int>q;
vector<int>e[xx];
inline void bfs(int s)
{
    dis[s][s]=0;
    vis[s]=s;
    q.push(s);
    while(!q.empty())
    {
        int g=q.front();
        q.pop();
        fur(i,0,(int)e[g].size()-1)
        {
            if(vis[e[g][i]]!=s)
            {
                dis[s][e[g][i]]=dis[s][g]+1;
                vis[e[g][i]]=s;
                q.push(e[g][i]);
            }
        }
    }
}
int main()
{
    int n=in,m=in;
    fur(i,1,m)
    {
        int x=in,y=in;
        e[x].push_back(y);
        e[y].push_back(x);
    }
    memset(dis,0x7f,sizeof(dis));
    fur(i,1,n) bfs(i);
    int s1=in,t1=in,l1=in;
    int s2=in,t2=in,l2=in;
    if(dis[s1][t1]>l1||dis[s2][t2]>l2)
    {
        puts("That's all trouble!");
        return 0;
    }
    int ans=dis[s1][t1]+dis[s2][t2];
    fur(i,1,n)
    {
        fur(j,1,n)
        {
            if(dis[i][j]+dis[s1][i]+dis[j][t1]<=l1&&dis[i][j]+dis[j][t2]+dis[s2][i]<=l2)
                ans=min(ans,dis[i][j]+dis[s1][i]+dis[j][t1]+dis[j][t2]+dis[s2][i]);
            if(dis[j][i]+dis[s1][j]+dis[i][t1]<=l1&&dis[j][i]+dis[s2][i]+dis[j][t2]<=l2)
                ans=min(ans,dis[j][i]+dis[s1][j]+dis[s2][i]+dis[i][t1]+dis[j][t2]);
        }
    }
    printf("%d\n",m-ans);
    return 0;
}

当然,上述程序中最好用邻接表代替\(vector\),考试时\(vector\)有被卡的风险

prob1:点对计数(xdis)

题目大意:给定一张无向图,边带边权,其中一点到另一点经过某一路径的代价定义为该路径上的边权最大值,而一点到另一点的最短路为代价最小值,求最短路长度为\(x\)的点对数

正解:再次感叹一波\(ljq\)大佬的智慧,他深深地折服了我

题中的边权均为整数,而虽然长度为\(x\)的不好直接求,但若要得到长度不超过\(x\)却很好搞(将大于\(x\)的边都删去,计算剩下的边所形成的联通块的大小,若一个联通块大小为\(cnt_i\),则将带来\(C_{cnt_i}^2\)的贡献,仔细想想,不难证明这样是正确的),这样的话可以先计算最短路不大于\(x\)的点对数,再计算最短路不大于\(x-1\)的点对数,轻松求解。

结果我考场上还去跑什么\(SPFA\),还瞎推了一波什么式子,最后面\(T\)飞,没\(T\)的还全\(WA\)……

不说了,伤心,上代码(还是太菜了,刚才一个并查集都调了半天):

#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
#define in read()
#define fur(i,a,b) for(int i=a;i<=b;i++)
#define xx 330000
#define ll long long
inline ll read()
{
    ll x=0,f=1;char ch=getchar();
    for(;!isalnum(ch);ch=getchar()) if(ch=='-') f=-1;
    for(;isalnum(ch);ch=getchar()) x=x*10+ch-'0';
    return x*f;
}
struct edge{ll u,v,val;}d[xx];
ll fa[xx],n,m,cnt[xx];
inline ll find(int x){return (fa[x]==x)?x:(fa[x]=find(fa[x]));}
inline bool cmp(edge x,edge y){return x.val<y.val;}
inline ll sol(ll x)
{
    fur(i,1,n) fa[i]=i,cnt[i]=0;
    fur(i,1,m)
    {
        if(d[i].val>x) break;
        if(find(fa[d[i].u])!=find(fa[d[i].v])) fa[find(d[i].u)]=find(d[i].v);
    }
    fur(i,1,n) cnt[find(i)]++;
    ll res=0;
    fur(i,1,n) if(cnt[i]) res+=cnt[i]*(cnt[i]-1)/2;
    return res;
}
int main()
{
    n=in,m=in;
    ll k=in;
    fur(i,1,m)
    {
        ll x=in,y=in,z=in;
        d[i]=(edge){x,y,z};
    }
    sort(d+1,d+m+1,cmp);
    ll ans1=sol(k);
    ll ans2=sol(k-1);
    printf("%lld\n",ans1-ans2);
    return 0;
}

注意!注意!前方高能

然而,最难的题却有了最水的数据(所以实际上这道题也不是那么恶心)。

prob4:最小生成树(graph)

题目大意:开始有一张有\(n\)个节点、没有边的图,后有m个操作,共三种,设操作编号为\(i\),则第一种\(ADD(a,b)\)为在\(a,b\)间添加一条长度为\(i\)的边,第二种\(DELETE(k)\)为删除前\(k\)大的边,最后一种\(RETURN\),为撤销第\(i-1\)项操作,保证该操作不为\(RETURN\)。每次操作后输出该图最小生成树的边权总和,若无最小生成树,输出\(0\)

开始想用单调队列做,结果\(CE\)了,无奈……

后来学长说是傻逼题,思路也很简单,就如同我发现的那样(不要脸)。但后来并没有付诸于实践,因为时间不够了(都去搞\(T1\)了啊!!)。

无能狂怒……

还是讲题吧,正解:首先要明确三个性质:\(1.\)若一条边在加入时并未被最小生成树选中,那他一辈子都不会被选中;\(2.\)删边只删最后加入的\(k\)条边;\(3.\)返回只返回到前一个版本。大体帮助解题的就这三条,又因为这题不要求强制在线,那么一个清晰的模板就出来了:首先记录每一时刻的答案,因为加边在生成树成形后不影响,那么使用之前的答案不影响正确性,而该边可丢弃(记得\(tag\)),若生成树暂未成型,则暴力跑;删边则将状态还原成在加入最后\(k\)条边之前的状态,答案已记录,故用该答案;而撤销则提前记录好,加边撤销就先加边,再暴力跑,再弹出,删边撤销一样可以\(yy\)出来,两种都可用先前答案替代。\(ljq\)说:“难度其实并不大。”

主要知识点:并查集优化之二——按秩合并

通过子树\(size\)的大小来玄学压缩树高以达到节省时间的目的

(吐槽一下,数据有\(30\)分的部分分(只有\(ADD\)操作),但打好了这个操作的代码可以过\(90\)分,这数据水得简直无力吐槽,真香,看了下样例答案,好像是\(m\)\(0\),对于这种数据而我又是\(CE\),当场去世……)

上代码:

#include<iostream>
#include<cstdio>
using namespace std;
#define in read()
#define fur(i,a,b) for(int i=a;i<=b;i+=1)
#define ll long long
#define xx 500010
inline int read()
{
    int x=0,f=1;char ch=getchar();
    for(;!isalnum(ch);ch=getchar()) if(ch=='-') f=-1;
    for(;isalnum(ch);ch=getchar()) x=x*10+ch-'0';
    return x*f;
}
ll fa[xx],sta[xx],ans[xx],id[xx],sz[xx];
bool choose[xx];
struct prom{ll u,v,op,k;}b[xx];
inline ll find(ll x){return fa[x]?find(fa[x]):x;}
int main()
{
    ll n=in,m=in,top=0,cnt=0,length=0;
    char ch[10];
    fur(i,1,n) sz[i]=1;
    fur(i,1,m)
    {
        scanf("%s",ch);
        if(ch[0]=='R') b[i].op=3;
        if(ch[0]=='A') b[i].op=1,b[i].u=in,b[i].v=in;
        if(ch[0]=='D') b[i].op=2,b[i].k=in;
    }
    fur(i,1,m)
    {
        if(b[i].op==1)
        {
            ll a=find(b[i].u),c=find(b[i].v);
            if(sz[a]>sz[c]) swap(a,c);
            id[++top]=i;
            sta[top]=a;
            if(a!=c)
            {
                fa[a]=c;
                sz[c]+=sz[a];
                ++cnt;
                length+=i;
                choose[i]=true;
            }
            if(cnt>=n-1) ans[i]=length;
        }
        if(b[i].op==2)
        {
            if(b[i+1].op!=3)
            {
                fur(j,1,b[i].k)
                {
                    if(choose[id[top]])
                    {
                        --cnt;
                        length-=id[top];
                        sz[fa[sta[top]]]-=sz[sta[top]];
                        fa[sta[top]]=0;
                    }
                    top-=1;
                }
                if(cnt>=n-1) ans[i]=length;
            }
            else ans[i]=ans[id[i-b[i].k+1]-1];
        }
        if(b[i].op==3)
        {
            if(b[i-1].op==1)
            {
                if(choose[id[top]])
                {
                    --cnt;
                    length-=id[top];
                    sz[fa[sta[top]]]-=sz[sta[top]];
                    fa[sta[top]]=0;
                }
                top-=1;
            }
            ans[i]=ans[i-2];
        }
        printf("%lld\n",ans[i]);
    }
    return 0;
}

这篇题解写得出乎意料的久啊,希望以后别再\(dabai\)

猜你喜欢

转载自www.cnblogs.com/ALANALLEN21LOVE28/p/11312981.html