2019 ICPC Asia-East Continent Final

2019 ICPC Asia-East Continent Final

A. City

链接:https://codeforces.com/gym/102471/problem/A

题意:给定 n × m n\times m n×m 的网格图,有 ( n + 1 ) × ( m + 1 ) (n+1)\times (m+1) (n+1)×(m+1) 个点,问有多少条线段满足两个端点在网格点上,且中点也在网格点上

思路:竖的和横的比较好算,斜着的只需要偶数偶数匹配一下就好了。

#include <bits/stdc++.h>
#define ll long long
using namespace std;
int n,m;
int main()
{
    scanf("%d%d",&n,&m);
    ll ans=0;
    for(int len=2; len<=n; len+=2)
        ans+=(n-len+1)*(m+1);
    for(int len=2; len<=m; len+=2)
        ans+=(m-len+1)*(n+1);
    for(int len1=2; len1<=n; len1+=2)
        for(int len2=2; len2<=m; len2+=2)
            ans+=(n-len1+1)*(m-len2+1)*2;
    printf("%lld\n",ans);
    return 0;
}

E. Flow

链接:https://codeforces.com/gym/102471/problem/E

题意:给定 n 个点 m 条边的有向图,从 1 到 n 会形成 k 条等长的路径,每条边上都有容量。现在可以将其中一条边容量 -1 ,另一条边的容量 + 1 。问至少操作几次可以使从 1 到 n 流过的流量最大

思路

  • 第一眼看到:感觉每条边的流量应该是:总流量平均到每一条边 = t o t m \frac {tot}{m} mtot,但其实这样是错的,多余的部分,依然可以在集中在一条路径上。一条路径的边数为: c n t = m k cnt = \frac mk cnt=km ,那么 k 条路径合在一起达到的平均值应该是: t o t c n t \frac {tot}{cnt} cnttot,这个才是最终的最大流量。
  • 那么就可以将 k 条路径当成一条来看。把每条路径的容量,从小到大排序,然后把 k 条路径合在一起。此时,平均流量和总容量的差值,就是需要操作的次数。
  • 这里我们无需关心,这个操作是怎么实现的,只需要关注当前状态和最终状态之间的差值即可
#include <bits/stdc++.h>
#define fi first
#define se second
#define ll long long
using namespace std;
const int maxn=1e5+10;
int n,m;
vector<pair<int,int> > e[maxn];
vector<int> we[maxn];
int main()
{
    scanf("%d%d",&n,&m);
    ll tot=0;
    for(int i=1;i<=m;++i)
    {
        int u,v,w;
        scanf("%d%d%d",&u,&v,&w);
        e[u].push_back({v,w});
        tot+=w;
    }
    int cnt=e[1].size();
    for(int i=0;i<cnt;++i)
    {
        auto x=e[1][i];
        int u=x.fi;
        int w=x.se;
        we[i].push_back(w);
        while(u!=n)
        {
            w=e[u][0].se;
            u=e[u][0].fi;
            we[i].push_back(w);
        }
    }
    for(int i=0;i<cnt;++i) sort(we[i].begin(),we[i].end());
    ll ans=0;
    ll avg=tot/we[0].size();
    for(int i=0;i<we[0].size();++i)
    {
        ll sum=0;
        for(int j=0;j<cnt;++j) sum+=we[j][i];
        ans+=max(0ll,avg-sum);
    }
    printf("%lld\n",ans);
    return 0;
}

M. Value

链接:https://codeforces.com/gym/102471/problem/M

题意:设 A 是 { 1 , 2 , … , n } \{1,2,\dots,n\} {1,2,,n} 的一个子集。初始分数为 0 ,对每一个 i ∈ A i \in A iA ,增加 a i a_i ai 的分数,对于任意二元组 { i , j } \{i,j\} {ij} 满足 i k = j i^k=j ik=j ( k > 1 ) (k>1) k>1,则减去 b j b_j bj 。问怎样选择可以使得集合 A 的分数最大。这个最大分数是多少。 ( 1 ≤ n ≤ 1 0 5 , 1 ≤ a i , b i ≤ 1 0 9 ) (1\le n \le 10^5,1\le a_i,b_i\le 10^9) (1n105,1ai,bi109)

思路

  • 根据幂可以这样分开来看: i , i 2 , i 3 , … i k i , i^2,i^3,\dots i^k i,i2,i3,ik ,然后需要选择一个子集,使得获得的分数最大。
  • 从后往前贪心:假设选择了 i 8 i^8 i8 ,当遇到 i 4 i^4 i4 的时候,发现 b[i^8] 特别大。此时,是选择保留: i 8 i^8 i8 还是 i 4 i^4 i4 ,我们是无法判断的。
  • 不能贪心,不能DP。没有最优子结构,然后会产生后效性。
  • 所以只能最暴力的方式了:直接枚举子集
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn=1e5+10;
int n,a[maxn],b[maxn];
int visit[maxn];
ll solve(ll x)
{
    ll k=x;
    vector<int> vec;
    while(k<=n)
    {
        visit[k]=1;
        vec.push_back(k);
        k*=x;
    }
    int cnt=vec.size();
    ll ans=0;
    for(int i=0; i<(1<<cnt); ++i)
    {
        vector<int> cur;
        for(int j=0; j<cnt; ++j)
            if(i>>j&1) cur.push_back(vec[j]);
        ll res=0;
        int m=cur.size();
        for(int i=0; i<m; ++i)
        {
            res+=a[cur[i]];
            for(int j=i+1; j<m; ++j)
            {
                ll tmp1=cur[i],tmp2=cur[j];
                while(tmp1<tmp2) tmp1*=cur[i];
                if(tmp1==tmp2) res-=b[cur[j]];
            }
        }
        ans=max(ans,res);
    }
    return ans;
}
int main()
{
    scanf("%d",&n);
    for(int i=1; i<=n; ++i) scanf("%d",&a[i]);
    for(int i=1; i<=n; ++i) scanf("%d",&b[i]);
    ll ans=a[1];
    for(int i=2; i<=n; ++i)
        if(!visit[i]) ans+=solve(i);
    printf("%lld",ans);
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值