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
<
=
i
<
=
x
)
dp[0][x] = min(max(dp[0][i-1]+b,dp[0][x-i]+a))_{(1<=i<=x)}
dp[0][x]=min(max(dp[0][i−1]+b,dp[0][x−i]+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
<
=
i
<
=
x
)
dp[1][x] = min(max(dp[1][i-1]+b,dp[0][x-i]+a))_{(1<=i<=x)}
dp[1][x]=min(max(dp[1][i−1]+b,dp[0][x−i]+a))(1<=i<=x)
直接转移的复杂度是
O
(
n
2
)
O(n^2)
O(n2)的,考虑优化。
易得:
d
p
[
0
]
[
i
+
1
]
>
=
d
p
[
0
]
[
i
]
,
d
p
[
1
]
[
i
+
1
]
>
=
d
p
[
1
]
[
i
]
dp[0][i+1]>=dp[0][i],dp[1][i+1]>=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
*/