AtCoder-ABC-190(A-F)

A

输入A,B,C,C决定先后手,A和B每次轮流减1,先减为0的输。

根据先后手决定判断是小于还是小于等于。

B

有一些法术,施法需要Xi秒,攻击力Yi,怪兽可以无视施法大于等于S秒或者是攻击力小于等于D的法术,问能否使怪兽造成伤害。

遍历判断,必须两个条件都不符合。

C

有N个盘子,M个条件,要求某两个盘子上都要有球,有K个操作,可以给两个盘子的其中一个放上一个球,问怎么放能满足最多条件。

K<=16,直接爆搜,然后枚举所有条件进行判断。

D

求出和为N的公差为1的整数等差数列的数量。

根据定义,该数列首尾相加乘以长度就是2N,是一个偶数,所以直接枚举2N的因子,再排除掉两个因子都是偶数(如果数列长度是偶数,而公差又是1,则首尾相加不可能是偶数)的情况,剩下的就是所有可能的数列。

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
ll n;
int main(){
    scanf("%lld",&n);
    n*=2LL;
    ll ans=0;
    for(ll i=1LL;i*i<=n;i++){
        if(n%i==0){
            if(i%2==1 || (n/i)%2==1){
                ans+=2;
            }
        }
    }
    printf("%lld\n",ans);
    return 0;
}

E

问题抽象出来就是给一个1e5的无向图,边权都为1,指定K个特殊点,要求找到最短的路径满足每个特殊点至少经过一次。

特殊点和特殊点之间肯定是走最短路径,所以可以先枚举特殊点,通过bfs求出任意两个特殊点的最短距离,这样就可以对图进行简化,其他的点都不需要,只需要关注这K个特殊点和它们之间的距离。

然后就是转化为带权的最短哈密顿通路的问题,因为K<=17,所以可以用状压dp来解决这个问题。

枚举起点,定义dp[i][j]表示已经过的点状态为i,最后经过的点是j的最短距离,枚举状态,再枚举上一个经过的点,状态转移方程为dp[j][l]=min(dp[j][l],dp[(j^(1<<(l-1)))][o]+cost[o][l])

注意状压dp中的位运算。

#include <bits/stdc++.h>
using namespace std;
const int N=1e5+50;
const int INF=0x3f3f3f3f;
int n,m,a,b,k;
int c[20];
vector<int> g[N];
int vis[N];
int cost[20][20],dp[1<<19][20];
int main(){
    scanf("%d%d",&n,&m);
    for(int i=1;i<=m;i++){
        scanf("%d%d",&a,&b);
        g[a].push_back(b);
        g[b].push_back(a);
    }
    scanf("%d",&k);
    for(int i=1;i<=k;i++){
        scanf("%d",&c[i]);
    }
    if(k==1){
        printf("%d\n",1);
        return 0;
    }
    for(int i=1;i<=k;i++){
        for(int j=1;j<=k;j++){
            if(i==j){
                cost[i][i]=0;
                continue;
            }else{
                cost[i][j]=INF;
            }
            int st=c[i];
            int ed=c[j];
            queue<pair<int,int>> q;
            queue<int> vq;
            q.push({st,0});
            vis[st]=1;
            vq.push(st);
            while(!q.empty()){
                auto t=q.front();
                q.pop();
                int u=t.first;
                int cs=t.second;
                if(u==ed){
                    cost[i][j]=cs-1;
                    break;
                }
                int siz=g[u].size();
                for(int l=0;l<siz;l++){
                    int v=g[u][l];
                    if(!vis[v]){
                        q.push({v,cs+1});
                        vis[v]=1;
                        vq.push(v);
                    }
                }
            }
            while(!vq.empty()){
                vis[vq.front()]=0;
                vq.pop();
            }
        }
    }
    int ans=INF;
    for(int i=1;i<=k;i++) {
        memset(dp,INF,sizeof(dp));
        // 起点
        dp[1<<(i-1)][i]=0;
        for(int j=1;j<(1<<k);j++){
            for(int l=1;l<=k;l++){
                if((j>>(l-1))&1){
                    // 枚举上一个经过的点
                    for(int o=1;o<=k;o++){
                        if(((j^(1<<(l-1)))>>(o-1))&1){
                            dp[j][l]=min(dp[j][l],dp[(j^(1<<(l-1)))][o]+cost[o][l]);
                        }
                    }
                }
            }
        }
        for(int j=1;j<=k;j++){
            if(j!=i){
                ans=min(ans,dp[(1<<k)-1][j]);
            }
        }
    }
    printf("%d\n",ans>=INF?-1:ans+k);
    return 0;
}

F

给定一个全排列,计算分别循环左移0到n-1位时候序列的逆序数。

因为是全排列,所以当一个数从首位左移到末位,显然逆序数的变化就是减去后面比它小的数的个数,再加上后面比它大的数的个数。

#include <bits/stdc++.h>
using namespace std;
const int N=3e5+50;
typedef long long ll;
int n,a[N];
int c[N];
int lowbit(int x){
    return x&(-x);
}
void add(int i,int x){
    while(i<=n){
        c[i]+=x;
        i+=lowbit(i);
    }
}
int sum(int i){
    int ans=0;
    while(i){
        ans+=c[i];
        i-=lowbit(i);
    }
    return ans;
}
int main(){
    scanf("%d",&n);
    for(int i=1;i<=n;i++){
        scanf("%d",&a[i]);
    }
    ll ans=0;
    for(int i=n;i>=1;i--){
        ans+=1LL*sum(a[i]+1);
        add(a[i]+1,1);
    }
    printf("%lld\n",ans);
    for(int i=1;i<n;i++){
        ans=ans+n-1-2LL*a[i];
        printf("%lld\n",ans);
    }
    return 0;
}
目录