2019杭电多校第七场补题(1007,1008,1010)

1007:Getting Your Money Back(DP)

用DP[0][x]表示区间长度为x,区间中不一定有钱时,确认已经拿完所有钱的最小花费,DP[1][x]表示区间长度为x,区间中一定有钱时,确认已经拿完所有钱的最小花费。考虑转移:
此时钱的可能范围是[0,x],你选择取数量为i的钱,那么如果成功,花费a,并且剩下的区间[i,x]未知,且不知道其中有没有钱,确认它的代价是DP[0][x-i],所以成功的时候花费为a+dp[0][x-i],而失败的时候,花费b,并且剩下区间[0,i-1]未知,所以失败的花费为b+dp[0][i-1]。因为需要考虑最坏情况,取i的时候贡献为max(a+dp[0][x-i],b+dp[0][i-1]),因为i可以任意取,所以选一个让这个max值最小的i作为最小代价,即
d p [ 0 ] [ x ] = m i n ( m a x ( d p [ 0 ] [ i − 1 ] + b , d p [ 0 ] [ x − i ] + a ) ) ( 1 &lt; = i &lt; = x ) dp[0][x] = min(max(dp[0][i-1]+b,dp[0][x-i]+a))_{(1&lt;=i&lt;=x)} dp[0][x]=min(max(dp[0][i1]+b,dp[0][xi]+a))(1<=i<=x)
同理
d p [ 1 ] [ x ] = m i n ( m a x ( d p [ 1 ] [ i − 1 ] + b , d p [ 0 ] [ x − i ] + a ) ) ( 1 &lt; = i &lt; = x ) dp[1][x] = min(max(dp[1][i-1]+b,dp[0][x-i]+a))_{(1&lt;=i&lt;=x)} dp[1][x]=min(max(dp[1][i1]+b,dp[0][xi]+a))(1<=i<=x)
直接转移的复杂度是 O ( n 2 ) O(n^2) O(n2)的,考虑优化。
易得: d p [ 0 ] [ i + 1 ] &gt; = d p [ 0 ] [ i ] , d p [ 1 ] [ i + 1 ] &gt; = d p [ 1 ] [ i ] dp[0][i+1]&gt;=dp[0][i],dp[1][i+1]&gt;=dp[1][i] dp[0][i+1]>=dp[0][i]dp[1][i+1]>=dp[1][i]
所以dp[0][i]和dp[1][i]关于i是单调不减的。
画出它们的图像,并把dp[1][i]关于(x/2)对称,max(dp[0][i-1]+a,dp[0][x-i]+b)的图像大概是这样的:
在这里插入图片描述
红色部分是取max之后的图像。我们需要取那个最小的,就是在最低点取。
显然可以三分求解并不能三分求解!
因为这个函数会有大量连续的相等的点,而三分只能处理严格单调的先递减后递增(或先递增后递减)的函数。(WA一晚上的教训)
可以二分出dp[0][i-1]+a最后一次小于dp[1][x-i]+b的点和dp[0][i-1]+a第一次大于等于dp[1][x-i]+b的点,在其中取个min来转移。
仔细观察可以发现,每次对于下一次转移,相当于dp[1][x-i]+b向右平移了1个单位,这说明那个转移点也向右移动了,所以可以维护转移点的位置把复杂度降到 O ( n ) O(n) O(n)

#include<bits/stdc++.h>//教训1:三分不能有相等的地方
#define ll long long//教训2:优化为线性之后是先减后增,但是同样有相等的地方,相等的时候idx也要前进
using namespace std;
const int maxn = 2e5 + 50;
const ll inf = 0x3f3f3f3f3f3f3f3f;
ll dp[2][maxn];
ll a, b;
ll cal(int c, int i, int n){
    return max(dp[c][i-1]+b, dp[0][n-i]+a);
}
void sol(int n){
    int idx1 = 1, idx2 = 1;
    for(int i = 1; i <= n; ++i){
        dp[0][i] = cal(0, idx1, i);
        while(idx1 < i && cal(0, idx1+1, i) <= dp[0][i]) dp[0][i] = cal(0, ++idx1, i);
        dp[1][i] = cal(1, idx2, i);
        while(idx2 < i && cal(1, idx2+1, i) <= dp[1][i]) dp[1][i] = cal(1, ++idx2, i);
    }
}
int main()
{
    int T;
    cin>>T;
    while(T--){
        int x, y;
        scanf("%d%d%lld%lld", &x, &y, &a, &b);
        dp[0][0] = 0;
        dp[1][0] = a;
        int n = y-x;
        sol(n);
        printf("%lld\n", dp[!!x][n]);
    }
}
/*0 931 4457 28257*/

1008: Halt Hater(缩点思维)

没能成功get题解思想,但是在讨论的时候受到了CZQ老哥的启发,在老哥耐心的讲解了得到了一个感觉很巧妙的写法。
如果你在一个地方一直右拐,你可以无消耗的走遍一个小正方形:
在这里插入图片描述
因为右拐无消耗,所以可以把一个小正方形缩成一个点。考虑在正方形之间走。
1.进入上方的正方形的循环,直走一步就可以达到。
2.进入左上方的正方形循环,左走一步就可以达到。
3.进入左边的正方形循环,一直右拐到(0,-1),直走一步可以达到

最后推出,走到相邻的正方形代价是b,走到斜对角代价是a。
因为一次左转可以完全代替一次直走,所以b=min(a,b).(如果你要直走,你可以先左转再两次右转)。
(x,y)被四个正方形覆盖,只要走到其中一个正方形就可以。
所以问题转换成,走斜线消耗a,直线消耗b,求(0,0)走到(x,y)的最小花费。
讨论一下就可以了。

#include<bits/stdc++.h>
#define ll long long
using namespace std;
ll a, b;//left, straigt
ll f(ll x, ll y)
{
    if(2*b <= a){
        return (x+y)*b;
    }
    ll ans = min(x,y)*a;
    ll d = abs(x-y);
    if(d%2 == 0){
        return ans + min(d*a, d*b);
    }
    else{
        return ans + min(d*b, 2*a*(d/2) + b);
    }
}
int main()
{
	ll x, y;
	int T;cin>>T;
	while(T--){
        scanf("%lld%lld%lld%lld", &a, &b, &x, &y);
        b = min(b, a);
        ll ans = f(abs(x),abs(y));
        ans = min(ans, f(abs(x-1),abs(y)));
        ans = min(ans, f(abs(x-1),abs(y+1)));
        ans = min(ans, f(abs(x),abs(y+1)));
        printf("%lld\n", ans);
	}
}

1010:Just Repeat

问题很容易转换成:每个物品有a,b两个属性,A取能获得a的价值,B取能获得b的价值,一个人取了另一个人不能取,属性高的人赢,求最优策略下A最多可以比B大多少。
考虑两个物品<a1,b1>,<a2,b2>
如果A取<a1,b1>更优,需要满足(a1-b2>a2-b1),即(a1+b1>a2+b2)
所以按a+b从大到小依次拿就可以。
卡map真是太可恶了学会了unordered_map真是太好了。

#include<bits/stdc++.h>
#include<unordered_map>
#define ll long long
#define P pair<ll, ll>
using namespace std;
const int maxn = 1e5 + 50;
int n, m;
ll a[maxn], b[maxn];
P c[maxn];
int nc;
unordered_map<ll,ll> s1, s2;
unsigned long long k1, k2, mod;
unsigned long long rng() {
    unsigned long long k3 = k1, k4 = k2;
    k1 = k4;
    k3 ^= k3 << 23;
    k2 = k3 ^ k4 ^ (k3 >> 17) ^ (k4 >> 26);
    return k2 + k4;
}
ll na, nb;
bool cmp(P& x, P& y){
    return (x.first + x.second) > y.first+y.second;
}
void init(int op){
    na = nb = nc = 0;
    s1.clear(); s2.clear();
    if(op == 1){
        for(int i = 1; i <= n; ++i) cin>>a[i];
        for(int i = 1; i <= m; ++i) cin>>b[i];
    }
    else{
        cin>>k1>>k2>>mod;
        for (int i = 1; i <= n; ++i)
            a[i] = rng() % mod;
        cin>>k1>>k2>>mod;
        for (int i = 1; i <= m; ++i)
            b[i] = rng() % mod;
    }
    sort(a+1, a+n+1);
    sort(b+1, b+m+1);
    for(int i = 1; i <= n; ++i) s1[a[i]]++;
    for(int i = 1; i <= m; ++i) s2[b[i]]++;
    unordered_map<ll,ll>::iterator it;
    for(it = s1.begin(); it!= s1.end(); ++it){
        ll val = it->first;
        if(s2.find(val) == s2.end()){
            na += it->second;
        }
        else{
            c[nc++] = P(it->second, s2[val]);
            s2.erase(val);
        }
    }
    for(it = s2.begin(); it!= s2.end();++it){
        nb += it->second;
    }

}
void sol(){
    int cur = 0;
    sort(c, c+nc,cmp);
    ll lead = na-nb;
    for(int i = 0; i < nc; ++i){
        if(cur == 0) lead += c[i].first;
        else lead -= c[i].second;
        cur ^= 1;
    }
    if(lead > 0) cout<<"Cuber QQ\n";
    else cout<<"Quber CC\n";
    return;
}
int main()
{
    ios::sync_with_stdio(false);
    int T;cin>>T;
    while(T--){
        int op;
        cin>>n>>m>>op;
        init(op);
        sol();
    }
}
/*
1
5 6 1
1 1 1 2 3
1 1 2 3 5 6
*/

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值