AtcoderABC218(D~G)题解

T1 构成矩形(ABC218 D)

先预处理出所有与 x x x轴平行的边,然后暴力判断其中两条边能不能构成矩形。

#include 
using namespace std;
#define LL long long
const LL MAXN = 2005;
LL n;
struct node1 {
    LL x, y;
    bool operator<(const node1 &t) const { return x < t.x || (x == t.x && y < t.y); }
} a[MAXN];
struct node2 {
    LL xa, xb;
    LL ya, yb;
    LL len;
    bool operator<(const node2 &t) const {
        return len < t.len || (len == t.len && xa < t.xa) || (len == t.len && xa == t.xa && ya < t.ya);
    }
};
vector<node2> b;
int main() {
    scanf("%lld", &n);
    for (LL i = 1; i <= n; i++) {
        scanf("%lld%lld", &a[i].x, &a[i].y);
    }
    sort(a + 1, a + n + 1);
    for (LL i = 1; i <= n; i++) {
        for (LL j = i + 1; j <= n; j++) {
            if (a[i].y == a[j].y) {
                node2 temp;
                temp.len = a[j].x - a[i].x;
                temp.xa = a[i].x;
                temp.ya = a[i].y;
                temp.xb = a[j].x;
                temp.yb = a[j].y;
                b.push_back(temp);
            }
        }
    }
    sort(b.begin(), b.end());
    LL ans = 0;
    for (LL i = 0; i < b.size(); i++) {
        for (LL j = i + 1; j < b.size(); j++) {
            if (b[j].len != b[i].len)
                break;
            if (b[j].xa == b[i].xa)
                ans++;
        }
    }
    printf("%lld", ans);
}

T2 摧毁(ABC218 E)

把删边改为加边,问题转化为求加任意数量的边,使得最终图为连通图,最少能得到多少钱?

显然,最少的边数为 n − 1 n-1 n1,又要是连通图,就是最小生成树。

所以先标记选择的最小生成树上的边,然后在剩下的边里选择,得到最大的钱数。

#include
using namespace std;
#define LL long long
const LL MAXN=200005;
struct node{
    LL u,v,w;
    bool operator <(const node &t)const{
        return w<t.w;
    }
}a[MAXN];
LL n,m;
LL f[MAXN];
bool vis[MAXN];
LL fi(LL x){
    if(f[x]==x)return x;
    return f[x]=fi(f[x]);
}
int main(){
    scanf("%lld%lld",&n,&m);
    for(LL i=1;i<=m;i++){
        scanf("%lld%lld%lld",&a[i].u,&a[i].v,&a[i].w);
    }
    sort(a+1,a+m+1);
    for(LL i=1;i<=n;i++){
        f[i]=i;
    }
    LL cnt=0;
    for(LL i=1;i<=m;i++){
        LL fx=fi(a[i].u),fy=fi(a[i].v);
        if(fx!=fy){
            f[fx]=f[fy];
            vis[i]=true;
            cnt++;
        }
        if(cnt==n-1)break;
    }
    LL ans=0;
    for(LL i=1;i<=m;i++){
        if(!vis[i]){
            ans=max(ans,ans+a[i].w);
        }
    }
    printf("%lld",ans);
}

T3 断边(ABC218 F)

首先想到的是暴力去做,但是这样的时间复杂度错了,因为边数最大的情况为 n 2 n^2 n2左右,所以一次 B F S BFS BFS O ( n + m ) = O ( n 2 ) O(n+m)=O(n^2) O(n+m)=O(n2),这样一来时间复杂度就来到了 O ( n 4 ) O(n^4) O(n4)

所以我们需要进行一点小优化,第一次 B F S BFS BFS时可以对未删边时的最短路上的边进行标记,如果没有删掉这些边,那答案是与第一次 B F S BFS BFS一样的。这样就可以通过了。

#include
using namespace std;
#define LL long long
const LL MAXN=405;
const LL inf=10000000000;
LL n,m;
LL u[MAXN*MAXN],v[MAXN*MAXN];
vector<pair<LL,LL> >a[MAXN];
LL dis[MAXN];
queue<LL>q;
bool is[MAXN*MAXN];
pair<LL,LL> pre[MAXN*MAXN];
LL bfs(LL del){
    while(!q.empty())q.pop();
    q.push(1);
    for(LL i=1;i<=n;i++){
        dis[i]=inf;
    }
    dis[1]=0;
    while(!q.empty()){
        LL t=q.front();q.pop();
        for(LL i=0;i<a[t].size();i++){
            LL xx=a[t][i].first;
            LL yy=a[t][i].second;
            if(del==yy)continue;
            if(dis[xx]>dis[t]+1){
                dis[xx]=dis[t]+1;
                q.push(xx);
                if(del==0)pre[xx]={t,yy};
            }
        }
    }
    if(dis[n]>=inf)dis[n]=-1;
    return dis[n];
}
int main(){
    // freopen("C.in","r",stdin);
    scanf("%lld%lld",&n,&m);
    for(LL i=1;i<=m;i++){
        scanf("%lld%lld",&u[i],&v[i]);
        a[u[i]].push_back({v[i],i});
    }
    LL ans=bfs(0),now=n;
    while(now>1){//因为无解时now会变为0,所以不能写成now!=1
        is[pre[now].second]=true;
        now=pre[now].first;
    }
    for(LL i=1;i<=m;i++){
        if(is[i])printf("%lld\n",bfs(i));
        else printf("%lld\n",ans);
    }
}

T4 树上游戏(ABC218 G)

这是一道假的博弈论题目,分析一波发现小 A A A一定会选择子树中最大的那一个,小 B B B则会选择最小的。

所以可以 d f s dfs dfs遍历一遍树,维护当前路径上的中位数,这个离散化一下,用树状数组维护,二分查找中位数就行。

#include
using namespace std;
#define LL long long
const LL MAXN=100005;
LL n,m;
LL a[MAXN];
vector<LL>b[MAXN];
LL c[MAXN],f[MAXN];
LL s[MAXN],dep[MAXN];
LL d[MAXN];
LL lowbit(LL x){
    return x&(-x);
}
LL query(LL x){
    LL res=0;
    while(x){
        res+=s[x];
        x-=lowbit(x);
    }
    return res;
}
void add(LL x,LL k){
    while(x<=100000){
        s[x]+=k;
        x+=lowbit(x);
    }
}
void dfs(LL x,LL fa){
    dep[x]=dep[fa]+1;
    if(dep[x]&1)f[x]=0;
    else f[x]=1e9;
    bool flg=false;
    add(a[x],1);
    for(LL i=0;i<b[x].size();i++){
        LL xx=b[x][i];
        if(xx==fa)continue;
        flg=true;
        dfs(xx,x);
        if(dep[x]&1)f[x]=max(f[x],f[xx]);
        else f[x]=min(f[x],f[xx]);
    }
    if(!flg){
        LL l=1,r=m;
        while(l<r){
            LL mid=(l+r)>>1;
            if(query(mid)>=(dep[x]+1)/2)r=mid;
            else l=mid+1;
        }
        if(dep[x]&1)f[x]=c[r];
        else{
            if(query(r)>dep[x]/2)f[x]=c[r];
            else{
                LL temp=r;
                l=r+1;r=m;
                while(l<r){
                    LL mid=(l+r)>>1;
                    if(query(mid)-query(temp))r=mid;
                    else l=mid+1;
                }
                f[x]=(c[r]+c[temp])/2;
            }
        }
    }
    add(a[x],-1);
}
int main(){
    scanf("%lld",&n);
    for(LL i=1;i<=n;i++){
        scanf("%lld",&a[i]);
        c[i]=a[i];
        d[i]=a[i];
    }
    sort(c+1,c+n+1);
    m=unique(c+1,c+n+1)-c-1;
    for(LL i=1;i<=n;i++){
        a[i]=lower_bound(c+1,c+m+1,a[i])-c;
    }
    for(LL i=1;i<n;i++){
        LL u,v;
        scanf("%lld%lld",&u,&v);
        b[u].push_back(v);
        b[v].push_back(u);
    }
    dfs(1,0);
    printf("%lld",f[1]);
}

你可能感兴趣的:(图论,深度优先,算法)