D. Glass Half Spilled
題目連結:https://codeforces.com/contest/1459/problem/D
本題的意思就是給你 n n n個杯子的容量 a [ i ] a[i] a[i]和存有的水的量 b [ i ] b[i] b[i],每個被子裡的水都可以轉到其他杯子去,但是每次都要損失 b [ i ] 2 \frac{b[i]}{2} 2b[i],問我們從 n n n個杯子中取得 k k k個杯子,這些杯子中最多有多少水。
假設 n n n個杯子的容量有 S a S_a Sa,水有 S b S_b Sb, k k k個杯子有容量 S a k S_{ak} Sak,水 S b k S_{bk} Sbk
容易知道:我們固定 k k k個杯子後,剩下的杯子裡的水都要轉進來,則得到的最大的水為 m i n ( S a k , S b k + S a − S b k 2 ) min(S_{ak},S_{bk}+\frac{S_a-S_{bk}}{2}) min(Sak,Sbk+2Sa−Sbk)
其中如果知道是哪 k k k個杯子,那麼 S a k S_{ak} Sak和 S a k S_{ak} Sak都會得到,是以,問題轉為了要知道是哪幾個杯子。
對于給定的 n n n個杯子,要取得其中 k k k個杯子,确認過和背包問題很像。
是以我們首先想到 d p [ i ] [ j ] dp[i][j] dp[i][j]表示前 i i i個杯子中取 j j j個杯子得到的最大值。
但是再想想,我們得到的最大值雖然表示了在這個狀态下可能的最大值,但是我們知道是取哪幾個嗎?是真正的最大值嗎?
就舉個二維費用背包的例子,二維背包要枚舉體積和重量,不妨令 d p [ i ] [ j ] [ k ] dp[i][j][k] dp[i][j][k]表示前 i i i個物品體積為 j j j,重量為 k k k的狀态,隻有這樣才确定了我們在這得到的狀态,假如二維背包的 d p [ i ] [ j ] dp[i][j] dp[i][j]表示前 i i i個物品,體積為 j j j的狀态,我們能得到最優的 d p [ i + 1 ] [ j + v ] dp[i+1][j+v] dp[i+1][j+v]嗎?
答案是不能。因為可能我們得到體積 v v v的最好狀态卻浪費了大量的重量,如果我們根據這轉移,明顯不是最優解。即後續的最大值也會發生變化。
是以在這題裡,我們設三維 d p [ i ] [ j ] [ k ] dp[i][j][k] dp[i][j][k],表示前 i i i個物品,取 j j j個物品,容量是 k k k的狀态,這使得我們目前的狀态唯一了,即固定了取了哪幾個杯子。而很明顯,取的物品個數相當于二維背包得體積,容量相當于二維背包的重量。
又因為空間複雜度太大,可以用滾動數組優化,就像二維背包,後面2個參數倒序就行。
下面是代碼
#include<iostream>
#include<vector>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<iomanip>
#include<stdio.h>
#include<map>
using namespace std;
#define check(x) cout<<"x:"<<x<<" "
typedef long long ll;
const int MAXN = 105;
int dp[105][10005];
int a[MAXN],b[MAXN];
int main() {
int n;
cin >> n;int sa = 0,sb=0;
for (int i = 1;i <= n;i++) {
cin >> a[i] >> b[i];
sa += a[i];
sb += b[i];
}
memset(dp, -1, sizeof dp);
dp[0][0] = 0;
for (int i = 1;i <= n;i++) {
for (int j = i;j >0;j--) {
for (int k = sa;k >= a[i];k--) {
if (dp[j - 1][k - a[i]]!=-1) {
dp[j][k] = max(dp[j][k], dp[j - 1][k - a[i]] +b[i]);
}
}
}
}
for (int i = 1;i <= n;i++) {
double tp=0;
for (int j = 0;j <= sa;j++) {
if (dp[i][j] != -1)tp = max(tp, min(1.0 * j, (sb + 1.0*dp[i][j]) * 0.5));
}
cout << fixed << setprecision(10) << tp << " ";
}
}
D. Ceil Divisions
題目連結:https://codeforces.com/contest/1469/problem/D
這題意思就是給你一個 1 1 1 ~ n n n的數組,每次選擇2個位置 x , y x,y x,y, 1 ⩽ x , y ⩽ n ( x ≠ y ) 1\leqslant x,y\leqslant n~~(x\ne y) 1⩽x,y⩽n (x=y),使得數組中的 x x x位置的 a x a_x ax元素變為 ⌈ a x a y ⌉ \lceil\frac{a_x}{a_y} \rceil ⌈ayax⌉,最多操作 n + 5 n+5 n+5次使得數組中隻有1個2,其餘都是1
剛開始的思路肯定很簡單,就是二倍的往下,比如給你一個數組
1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 , 10 , 11 , 12 , 13 1,2,3,4,5,6,7,8,9,10,11,12,13 1,2,3,4,5,6,7,8,9,10,11,12,13,先設 a = ⌈ 13 2 ⌉ = 7 a=\lceil\frac{13}{2}\rceil=7 a=⌈213⌉=7,然後讓 [ 8 , 12 ] [8,12] [8,12]的數都與13除,使得 [ 8 , 12 ] [8,12] [8,12]的數都為1,然後令 ⌈ 13 7 ⌉ = 2 \lceil\frac{13}{7}\rceil=2 ⌈713⌉=2,再 ⌈ 2 7 ⌉ = 1 \lceil\frac{2}{7}\rceil = 1 ⌈72⌉=1,使得13的位置變為1,這很明顯就是節省了很多步驟,但是仔細想,我每次除2,在商的那個位置,我要操作2次,才能變為1,其餘的都操作1次,最後的結果就是 n + log 2 n n+\log_{2}n n+log2n,很明顯不符合題意。
我們繼續往下想,我們每次除2,得到 n + log 2 n n+\log_{2}n n+log2n,那我們每次多除一點,假如除 t t t,那麼我們得到的便是 O ( n + log t n ) O(n+\log_{t}n) O(n+logtn),但事實不是這樣, t t t如果很大,在一個位置為 k k k的數 a k a_k ak, ⌈ a k t ⌉ = 1 \lceil\frac{a_k}{t}\rceil=1 ⌈tak⌉=1時,就轉為了 O ( k ) O(k) O(k)的次數,是以 t t t作為常數,我們找不到(即我們不能每次除固定的數),是以我們就繼續想,要比2多,但是不是常數,要随着我們現在處理的 a k a_k ak大小有關,不斷嘗試,最後會得到 log 2 a k , a k \log_{2}a_k,\sqrt{a_k} log2ak,ak
等數,然後驗算,發現 a k \sqrt{a_k} ak
适合。
下面是代碼:
#include<iostream>
#include<vector>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<iomanip>
#include<stdio.h>
#include<map>
using namespace std;
#define check(x) cout<<"x:"<<x<<" "
typedef long long ll;
int main() {
int t;
cin >> t;
while (t--) {
int n;
cin >> n;
int a = n;
int cnt = 0;
int pre = n;
while (a!=2) {
int b = ceil(sqrt(a*1.0));
cnt += 2;
cnt += pre - b - 1;
pre = b;
a = b;
}
cout << cnt << endl;
a = n;
pre = n;
while (a != 2) {
int b = ceil(sqrt(a * 1.0));
for (int i = b + 1;i <= a - 1;i++) {
cout << i << " " << a << endl;
}
cout << a << " " << b << endl;
cout << a << " " << b << endl;
a = b;
}
}
}
A. Regular Bracket Sequence
題目連結:https://codeforces.com/contest/1469/problem/A
首先,要明白題目的意思,題目介紹對于給定的字元串,隻會出現一個" ( ( (" 和一個" ) ) )",其餘都是" ? ? ?",是以很容易想到,字元串長度為奇數,肯定不行,如果是偶數,那麼" ( ( (" 在最右或者 " ) ) )"在最左也不行。
這題就很快出來了。但是我們往深想,如果沒規定兩個括号的個數呢?
我們想,對于沒有" ? ? ?" 的字元串,我們找出合格的,就是申明變量 c n t = 0 cnt=0 cnt=0然後遇到左括号 c n t + + cnt++ cnt++,右括号 c n t − − cnt-- cnt−−,一旦 c n t < 0 cnt<0 cnt<0就不合格,到最後,如果 c n t > 0 cnt>0 cnt>0也不合格
那麼我們對于有" ? ? ?" 的能不能像它一樣處理呢?
我們對于" ? ? ?" 轉換,如果轉化為左括号,目的是為了消除右邊的右括号,轉化為右括号,目的是為了消除左邊的左括号。
我們繼續想,對于字元串 ( ? ? ? ? ? ? (?????? (??????我們為了消除左括号,肯定要令?中的一個變為右括号,那我們變哪個才最好呢?
答案是最後一個,為何,因為最後一個的右括号,它可以消除它前面的多餘的任意一個左括号,貪心的思維。
( ? ? ? ? ? ? \color{blue}(???\color{red}{?}\color{black}?? (??????,比如就把紅色問号這個變為右括号,那麼它隻能消除藍色的位置的左括号,但是黑色位置的消除不了。
是以我們知道如果字元串不平衡, c n t > 0 cnt>0 cnt>0時,最優解肯定是從右到左将問号變為右括号。
那麼同理,左括号一樣的情況。但是,我們如果同時處理2種括号,會複雜。因為不知道哪個臨界點 是左右括号的臨界點。是以,我們可以直接将所有問号先當作"("處理。周遊字元串,得到 c n t cnt cnt。
f l a g = { f a l s e , 如果 c n t 為奇數或者 c n t < 0 t r u e o r f a l s e , c n t ⩾ 0 flag = \begin{cases} false, & \text{如果}cnt\text{ 為奇數或者}cnt<0 \\ true~or~false, & cnt\geqslant 0 \end{cases} flag={false,true or false,如果cnt 為奇數或者cnt<0cnt⩾0
下面對于 c n t ⩾ 0 cnt\geqslant 0 cnt⩾0的情況,隻要将問号從右到左變為左括号, c n t − = 2 cnt-=2 cnt−=2,一旦 c n t = = 0 cnt==0 cnt==0就退出。
但是" ) ) ? ? ))?? ))??"這種情況不行,是以我們從左到右再跑一遍無問号的字元串,如果符合就可以,不符合就不行。
下面是代碼:
#include<iostream>
#include<vector>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<iomanip>
#include<stdio.h>
#include<map>
using namespace std;
#define check(x) cout<<"x:"<<x<<" "
typedef long long ll;
char ch[1005];
int main() {
int t;
cin >> t;
while (t--) {
cin >> ch;
int cnt = 0;
int len = strlen(ch);
for (int i = 0;i < len;i++) {
if (ch[i] == '(' || ch[i] == '?')cnt++;
else cnt--;
}
if (cnt < 0||cnt%2) { cout << "NO" << endl;continue; }
for (int i = len - 1;i >= 0;i--) {
if (cnt == 0)break;
if (ch[i] == '?') {
ch[i] = ')';
cnt -= 2;
if (cnt == 0)break;
}
}
bool f = true;
for (int i = 0;i < len;i++) {
if (ch[i] == '(' || ch[i] == '?')cnt++;
else cnt--;
if (cnt < 0) {
f = false; break;
}
}
if (f)cout << "YES" << endl;
else cout << "NO" << endl;
}
}
C. Busy Robot
題目連結:https://codeforces.com/contest/1463/problem/C
題目意思是給你 n n n組數字,每組包含一個時間點和位置,第 i i i個點的時間點設為 t [ i ] t[i] t[i],位置設為 p [ i ] p[i] p[i],當一個機器人在 t [ i ] t[i] t[i]到 t [ i + 1 ] t[i+1] t[i+1]經過 p [ i ] p[i] p[i]時,答案數+1,一旦接受一個指令,機器人肯定要将路走完,走的時候釋出的指令都不會聽從,停下來時釋出的指令才會接受。
很明顯,是個模拟題。但是這個模拟還是比較惡心的。
因為我們對于在 t [ i ] t[i] t[i]時的狀态,我們要先知道運動的方向,要知道是處于停止狀态還是走路狀态,然後是運動的起始點和終點。
是以我們用 f f f記錄方向,-1為反方向,1為正方向,0表示不動, p t pt pt記錄前一次停止的時間,初始化 t [ 1 ] t[1] t[1], p o s pos pos記錄前一次停止的位置, p r e x prex prex記錄在 t [ i − 1 ] t[i-1] t[i−1]到達的位置, t x tx tx表示在 t [ i ] t[i] t[i]時到達的位置, n t nt nt就是現在的時間 t [ i ] t[i] t[i],設定第 n + 1 n+1 n+1是因為第一個樣例的啟發。
#include<iostream>
#include<vector>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<iomanip>
#include<stdio.h>
#include<map>
using namespace std;
#define check(x) cout<<"x:"<<x<<" "
#define Min(x,y,z) min(x,min(z,y))
#define Max(x,y,z) max(x,max(z,y))
typedef long long ll;
const int MAXN = 1e5 + 5;
ll t[MAXN], p[MAXN];
ll change(ll prex,ll nowx) {
if (prex < nowx)return 1;
if (prex == nowx)return 0;
if (prex > nowx)return -1;
}
int main() {
ll T;
cin >> T;
while (T--) {
ll n;
cin >> n;
for (int i = 1;i <= n;i++) { cin >> t[i] >> p[i]; }
ll cnt = 0;
ll f;
ll pt = t[1];
ll pos = 0;
ll prex = 0;
ll tar = p[1],to=p[1];
ll nt, tx;
t[n + 1] = 1e17;p[n + 1] = 0;
f = change(prex, tar);
for (int i = 2;i <= n+1;i++) {
nt = t[i];
tx = (nt - pt) * f + pos;
if (tx * f >= to * f) {
tx = to;
pos = to;
to = p[i];
f = change(pos, to);
pt = nt;
}
if (min(prex, tx) <= tar && max(prex, tx) >= tar)cnt++;
prex = tx;tar = p[i];
}
cout << cnt << endl;
}
}
D. Pairs
題目連結:https://codeforces.com/contest/1463/problem/D
題目大意:你有 [ 1 , 2 n ] [1,2n] [1,2n]的數,以及一個數組 b b b,對于 [ 1 , 2 n ] [1,2n] [1,2n]你可以找劃分為 n n n對,每隊我們可以取最小值,也可以最大值,令取最小值的對數為 x x x,然後令所有對取得的結果組成 b b b數組,問 x x x能取幾種情況。
對于 b b b數組,我們發現取最小值最優的是從左往右取,最大值是從右往左取,因為 b b b數組是有序的。
我們設 a a a數組是 [ 1 , 2 n ] [1,2n] [1,2n]中去除 b b b數組裡 數的一個數組。
分析一波:假設我們不是上面這取法,舉例令 b [ i ] b[i] b[i]是取 m a x max max得到的, b [ i + 1 ] b[i+1] b[i+1]是取 m i n min min得到的。
是以有即 b [ i ] = m a x ( b [ i ] , a [ k ] ) , b [ i + 1 ] = m i n ( b [ i + 1 ] , a [ t ] ) b[i]=max(b[i],a[k]),b[i+1]=min(b[i+1],a[t]) b[i]=max(b[i],a[k]),b[i+1]=min(b[i+1],a[t]),又因為 b [ i ] < b [ i + 1 ] b[i]<b[i+1] b[i]<b[i+1],是以 a [ k ] < a [ t ] a[k]<a[t] a[k]<a[t],是以如果 b [ i ] = m i n ( b [ i ] , a [ t ] ) , b [ i + 1 ] = m a x ( b [ i + 1 ] , a [ k ] ) b[i]=min(b[i],a[t]),b[i+1]=max(b[i+1],a[k]) b[i]=min(b[i],a[t]),b[i+1]=max(b[i+1],a[k])也一定成功。
但是反過來卻不一定。
是以,我們知道從左到右是取最小值,從右到左取最小值。
那麼,我們可以對于一個 b [ i ] b[i] b[i],假如取最小值,可以取幾次呢,設在 a a a數組中比 b [ i ] b[i] b[i]大的有 s s s個。這說明什麼,對于 b [ i + 1 − s ] b[i+1-s] b[i+1−s]開始到 b [ i ] b[i] b[i]取最小值,都是可以的。然後其他也一樣。
下面是代碼
#include<iostream>
#include<vector>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<iomanip>
#include<stdio.h>
#include<map>
using namespace std;
#define check(x) cout<<"x:"<<x<<" "
#define Min(x,y,z) min(x,min(z,y))
#define Max(x,y,z) max(x,max(z,y))
typedef long long ll;
const int MAXN = 4e5 + 5;
int a[MAXN], b[MAXN];
int pre[MAXN], last[MAXN];
int tong[MAXN];
int main() {
std::ios::sync_with_stdio(false);
int t;
cin >> t;
while (t--) {
int n;
cin >> n;
for (int i = 1;i <= n;i++) {
pre[i] = last[i] = 0;
cin >> a[i];
tong[a[i]] = 1;
}
int cnt = 0;
int tnt = 1;
for (int i = 1;i <= 2*n;i++) {
if (!tong[i])b[++cnt] = i;
}
cnt = 1, tnt = 1;
for (int i = 1;i <= n;i++) {
pre[i] = pre[i - 1];
while (b[tnt] < a[i]&&tnt<=n) {
pre[i]++;
tnt++;
}
}
tnt = n;
for (int i = n;i >= 1;i--) {
last[i] = last[i + 1];
while (b[tnt] > a[i]&&tnt>=1) {
last[i]++;
tnt--;
}
}
int r = 2e6;
int l = 0;
for (int i = 1;i <= n;i++) {
tong[a[i]] = 0;
r = min(r, i + last[i] - 1);
int j = n - i + 1;
l = max(l, j - pre[j]);
}
for (int i = 0;i <= n+1;i++)pre[i] = last[i] = 0;
cout << max(r - l + 1, 0) << endl;
}
}
D. 13th Labour of Heracles
題目連結:https://codeforces.com/contest/1466/problem/D
題目大意:給你一棵樹,每個節點都有權值,要你給這棵樹的邊染色,從一種顔色到 n − 1 n-1 n−1種顔色。要求得到所有不同顔色的分支的權值和的最大值,要保證每種顔色的子圖要連通。
首先,當顔色為1時,很明顯,答案就是所有點的權值和,顔色為2的時候,我們要分情況了,對于度(入度+出度)大于等于2的點,我們可以選一個點為中心,然後這個點有多條邊,一條邊引出去的樹為color1,其他為color2,這樣可以使得這個點被加了2次。
這也明顯得到,為了讓權值大的點被多加幾次,這個點的度數肯定要滿足一定條件。
度數為1最多加1次,度數為2最多加2次,度數為3最多加3次。。。。。。
下面是代碼:
#include<iostream>
#include<vector>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<iomanip>
#include<stdio.h>
#include<map>
#include<queue>
using namespace std;
#define check(x) cout<<"x:"<<x<<" "
#define Min(x,y,z) min(x,min(z,y))
#define Max(x,y,z) max(x,max(z,y))
typedef long long ll;
const int MAXN = 1e5 + 5;
ll a[MAXN], b[MAXN];
struct Node {
int du=0, val=0;
bool operator<(const Node& a)const {
return val > a.val;
}
}node[MAXN];
queue<Node>q;
int main() {
int t;
cin >> t;
while (t--) {
int n;
cin >> n;
ll s = 0;
for (int i = 1;i <= n;i++) {
cin >> a[i];
s += a[i];
node[i].val = a[i];
node[i].du = 0;
}
for (int i = 1;i < n;i++) {
int u, v;
cin >> u >> v;
node[u].du++;
node[v].du++;
}
sort(node + 1, node + 1 + n);
cout << s << " ";
int tnt = 2;
for (int i = 1;i <= n;i++) {
if (node[i].du >= 2) {
for (int j = 2;j <= node[i].du && tnt <= n-1;j++) {
tnt++;
s += node[i].val;
cout << s << " ";
}
if (tnt == n)break;
}
}
cout << endl;
}
}
D. Grime Zoo
題目大意:給你一個隻含有0,1,?的字元串以及x和y。?可以變為0或者1,然後出現01子串,憤怒值+x,出現10子串,憤怒值+y。
假如我們 x > y x>y x>y,很明顯,我們希望01出現的少,那麼對于"?"集合的處理,肯定要使得問号變成的1在問号變成的0前面,即 000 、 100 、 110 、 111 000、100、110、111 000、100、110、111這種,而不會是 010 、 011 、 001 010、011、001 010、011、001這種。
同理, y < x y<x y<x就相反。
那麼我們現在其實就是處理一個問号的字首0,1和字尾0,1。對于一整個字元串的貢獻,我們可以将問号拿出來單獨算,然後,那些非問号的提前算,後面,我們隻需要對于問号進行單獨修改,比如0變成1,我們隻要将原來這個0對于整個字元串的貢獻減掉,加上1對于整個字元串的貢獻就行。然後再加上問号集合的貢獻就像。
#include<iostream>
#include<vector>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<iomanip>
#include<stdio.h>
#include<map>
#include<queue>
using namespace std;
#define check(x) cout<<"x:"<<x<<" "
#define Min(x,y,z) min(x,min(z,y))
#define Max(x,y,z) max(x,max(z,y))
typedef long long ll;
const int MAXN = 1e5 + 5;
string str;
ll x, y;
ll pre[MAXN][2];
ll last[MAXN][2];
vector<int>v;
int main() {
cin >> str;
cin >> x >> y;
int len = str.length();
ll s = 0;
for (int i = 0;i < len;i++) {
if (str[i] == '?') { v.push_back(i + 1); }
pre[i + 1][1] = pre[i][1];
pre[i + 1][0] = pre[i][0];
if (str[i] == '1') {
s += pre[i + 1][0] * x;
pre[i + 1][1]++;
}
if (str[i] == '0'){
s += pre[i + 1][1] * y;
pre[i + 1][0]++;
}
}
for (int i = len - 1;i >= 0;i--) {
last[i + 1][0] = last[i + 2][0];
last[i + 1][1] = last[i + 2][1];
if (str[i + 1] == '1')last[i + 1][1]++;
if (str[i + 1] == '0')last[i + 1][0]++;
}
/*for (int i = 1;i <= len;i++) {
cout << pre[i][0] << " " << pre[i][1] << endl;
cout << last[i][0] << " " << last[i][1] << endl;
}*/
ll minn = 1e18;
if (x > y) {
for (auto i : v) {
s += pre[i][1] * y+last[i][1]*x;
}
int num = v.size();
int tnt = 0;
minn = min(s, minn);
for (auto i : v) {
tnt++;
s -= pre[i][1] * y + last[i][1] * x;
s += pre[i][0] * x + last[i][0] * y;
s += tnt * (num - tnt) * y;
minn = min(s, minn);
s -= tnt * (num - tnt) * y;
}
cout << minn << endl;
}
else {
for (auto i : v) {
s += pre[i][0] * x + last[i][0] * y;
}
int num = v.size();
int tnt = 0;
minn = min(s, minn);
for (auto i : v) {
tnt++;
s -= pre[i][0] * x + last[i][0] * y;
s += pre[i][1] * y + last[i][1] * x;
s += tnt * (num - tnt) * x;
minn = min(s, minn);
s -= tnt * (num - tnt) * x;
}
cout << minn << endl;
}
}