codeforces 1295 題解
A. Display The Number
給你可顯示的總數,讓你湊個最大的數,很明顯,抽象的思想就是“好鋼要用在刀刃上”,如果可顯示的數字越大,所消耗的越小,他自然更好。
我們觀察一下各個數字的消耗情況:
| 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
|---|---|---|---|---|---|---|---|---|---|
| 6 | 2 | 5 | 5 | 4 | 5 | 6 | 3 | 7 | 6 |
數字 \(9\) 比數字 \(8,6\) 要大,消耗還不比他們多,所以答案中肯定不出現 \(8,6\) (單調隊列思想)。
這樣一看,答案中只有可能出現數字 \(1,7,9\) 。我們再思考一下,一個數字大,他怎么算大?每一位最大的數字大他就一定大?顯然不是,判斷兩個數哪個大,首先要比較位數(大數思想),位數大的自然也就更大。
我們枚舉一下,如果我們的可顯示總數只有 \([2,3]\) 個,自然都只能湊成一位數了,但如果有 \(4\) 個呢?我們可以用兩個 \(1\) 湊成兩位數 \(11\) ,如果我們有 \(6\) 個就能湊成 \(111\),有點感覺以后我們發現好像答案中也不會出現 \(9\) 了,因為“兩拳難敵四手”,湊成一個 \(9\) 的代價過高,不如用它多湊幾位 \(1\)。
事實上,如果給你偶數個可顯示總數,你就用他們都來湊 \(1\) 就行,最后剛剛好。如果給你奇數個,那你可以先湊一個 \(7\),把它放在最高位,這樣也就不會有剩下的了。可以證明答案中最多出現一個 \(7\),因為出現兩個或者以上,那不如用他們多湊幾個 \(1\)。詳細證明過程略,意思領會就好。
時間復雜度 \(O(n)\)。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef double db;
#define _for(i,a,b) for(int i = (a);i < b;i ++)
#define _rep(i,a,b) for(int i = (a);i > b;i --)
#define INF 0x3f3f3f3f3f3f3f3f
#define ZHUO 11100000007
#define SHENZHUO 1000000007
#define pb push_back
#define debug() printf("Miku Check OK!\n")
#define maxn 20003
#define X first
#define Y second
int main()
{
int t;
scanf("%d",&t);
while(t--)
{
int n;
scanf("%d",&n);
string rnt;
if(n&0x1)
n -= 3,rnt += '7';
while(n>0)
n -= 2,rnt += '1';
printf("%s\n",rnt.c_str());
}
return 0;
}
B. Infinite Prefixes
根據它給的那個 \(balance\) 公式,我們可以轉換一下:字符串 \(t\) 每出現一個 \(0\) ,其實就是 \(balance\)值 \(+1\),出現一個 \(1\) 就是 \(balance\) 值 \(-1\)。然后我們就能根據字符串 \(t\) 構造一個整數數組 \(a\)。我們就拿樣例 \(1\) 說事兒:
| t | 0 | 1 | 0 | 0 | 1 | 0 |
|---|---|---|---|---|---|---|
| a | 1 | 0 | 1 | 2 | 1 | 2 |
那這個數組 \(a\) 有啥子用?我們思考這個字符串 \(t\) 是怎么來的,沒錯,就是通過字符串 \(s\) 復讀來的,那么我們把這個 \(a\) 數組往下寫一寫,找找規律:
\(1,0,1,2,1,2,3,2,3,4,3,4......\)
復讀一遍,我們發現第 \(i(i≥1)\) 位這個數怎么來?我給你一個公式你看看對不對:
\(a_{k,i}=a_{1,i}+a_{1,n}×k\)
其中 \(a_{i,j}\) 代表第 \(i\) 組第 \(j\) 個。
那這樣我們就能在預處理出 \(a\) 以后快速計算出第 \(?\) 個數了。我們繼續觀察這個數組, \(a\) 數組里不同組但是組內相對位置相同則整體單調,所以只有末尾那個數為 \(0\) 的時候才有可能輸出 \(-1\)。只有末尾那個數為 \(0\) 還不夠,還要中間出現 \(x\) 這個數才輸出 \(-1\)。
處理完特殊情況以后我們再繼續思考,既然后面都是復讀的,而且單調增或者減,我們的 \(x\) 是不變的,那么 \(x\) 對於組內相對位置相同時,在不同組只有可能出現 \(1\) 次,因此我們就看第一組然后算后面的那些個組,該位置應該是什么數,如果能等於 \(x\) ,那說明 \(x\) 可以在該相對位置出現一次,否則就不能。
根據上面的公式隨便搞一搞算一算看看能不能整除,就能判斷 \(x\) 在該組內相對位置能否出現,能答案就 \(+1\) ,注意特殊考慮 \(0\) 的情況。
時間復雜度 \(O(n)\)
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef double db;
#define _for(i,a,b) for(int i = (a);i < b;i ++)
#define _rep(i,a,b) for(int i = (a);i > b;i --)
#define INF 0x3f3f3f3f3f3f3f3f
#define ZHUO 11100000007
#define SHENZHUO 1000000007
#define pb push_back
#define debug() printf("Miku Check OK!\n")
#define maxn 100003
#define X first
#define Y second
bool judge(int a,int b)
{
if(!a || (!a && !b))
return true;
if(!b)
return false;
return ((a/abs(a))*(b/abs(b)))>0 && !(a%b);
}
int T;
int main()
{
ios::sync_with_stdio(false);
cin >> T;
while(T--)
{
int n, x;string s;
cin >> n >> x >> s;
int a[maxn];
a[0] = 0;
_for(i,0,n)
a[i+1] = a[i] + ((!(s[i]-'0')) ? 1 : -1);
int rnt = 0;
if(!a[n])
{
_for(i,1,n+1)
if(a[i]==x)
rnt = -1;
if(rnt==-1)
{
printf("-1\n");
continue;
}
}
if(!x)
rnt ++;
_for(i,1,n+1)
{
int tmpx = x;
tmpx -= a[i];
if(judge(tmpx,a[n]))
rnt ++;
}
printf("%d\n",rnt);
}
return 0;
}
C. Obtain The String
既然是從字符串 \(s\) 中取子序列湊 字符串 \(t\) ,那就貪心的去湊就可以了:每一次從 \(s\) 中取出的字符串自然越長越好。
關鍵是我們不能對於每個 \(t\) 中的字符都往后面無腦掃一遍 \(s\) ,那是 \(O(n^2)\) 的時間復雜度,那我們就想點好方法快點掃不就完事兒了嗎!怎么快呢,我們預處理 \(dp[i][j]\) 表示在字符串 \(s\) 下標為 \(i\) 的地方,后面最靠近該位置的字母 \(j+'a'\) 的下標是多少 ,這樣到時候我們就能跳着掃 \(s\) ,這樣就快的多了。
輸出 \(-1\) 的情況既很好判斷也很好理解:字符串 \(t\) 中出現了 字符串 \(s\) 中沒有的字母自然就湊不出來咯!
時間復雜度 \(O(max(|s|,|t|))\) 。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef double db;
#define _for(i,a,b) for(int i = (a);i < b;i ++)
#define _rep(i,a,b) for(int i = (a);i > b;i --)
#define INF 0x3f3f3f3f3f3f3f3f
#define ZHUO 11100000007
#define SHENZHUO 1000000007
#define MIKUNUM 39
#define pb push_back
#define debug() printf("Miku Check OK!\n")
#define maxn 100003
#define X first
#define Y second
//MIKUNUM是我喜歡的數字,沒啥意義,你也可以改成,40,50,或者30,都行
int T;
string s, t;
int latest[MIKUNUM];
int dp[maxn][MIKUNUM];
bool judge()
{
_for(i,0,t.size())
if(latest[t[i]-'a']==-1)
return false;
return true;
}
int main()
{
ios::sync_with_stdio(false);
cin >> T;
while(T--)
{
cin >> s >> t;
//這里初始化的可能有點多了,放在程序最后然后根據當前輸入量初始化能省點時間
memset(latest,-1,sizeof(latest));
memset(dp,-1,sizeof(dp));
_rep(i,s.size()-1,-1)
{
_for(j,0,26)
dp[i][j] = latest[j];
latest[s[i]-'a'] = i;
}
if(!judge())
{
printf("-1\n");
continue;
}
int rnt = 0;
int s_in = -1;
_for(i,0,t.size())
{
if(s_in==-1)
{
rnt ++;
s_in = latest[t[i]-'a'];
continue;
}
s_in = dp[s_in][t[i]-'a'];
(s_in == -1) ? i-- : MIKUNUM;
}
printf("%d\n",rnt);
}
return 0;
}
D. Same GCDs
數論題,卓爺的鍋,我就不背了,\(\%ZHUO\)
E. Permutation Separation
我們看到這題,自然而然應該就會想到兩個問題:
- 划分的依據是什么(划分完左右各有哪些元素)
- 如何移動是最優的
如果能把這兩個子問題搞定,那答案也就不難出來了。我們再讀題,他給了個排列,也就是數組 \(p\) 里的元素是從 \(1\) 到 \(n\) 的,那我們現在看第一個子問題,全都移動好以后左面無非就是 \(1\) ,\(1,2\) ,\(1,2,3\) ,\(1,2,3,4\) ... ...這些個情況。
我們再考慮,現在隨便選一個位置,那么左邊要移動什么到右邊,右邊要移動什么到左邊就確定了。比如你說左邊有 \(3\) 個數,那無非左邊就是 \(1,2,3\) ,所以如果 \(1,2,3\) 有在你分的地方的右邊那么就要要移動到左邊,左邊除了這三個元素都要移動到右邊。敲定一開始的划分位置和最終左邊數組的大小,暴力 \(O(n^2)\) 算法成了,但數據范圍肯定 \(T\) 飛。
考慮一下怎么優化,我們搞個代價數組 \(a\) 的前綴和數組出來,然后求下標為 \([1,n-1]\) 的最小值即可。這是什么?這就是你假設全移動完以后左邊是空着的,所有數都在右邊的答案,取最小值其實就是枚舉一開始在哪兒划分的過程。那我們只要枚舉左邊數組的大小,然后答案取各個不同數組大小情況下的最小值不就行了嗎?(之所以是 \([1,n-1]\) ,是因為我們假設划分是在該下標的數的右邊,如果取 \(n\),說明一開始就划分出一個 “一邊空”,那自然不符合題意)。
枚舉左邊數組大小那必定對時間復雜度有個 \(n\) 的貢獻沒跑了,不過我們區間查最小值可以優化到 \(logn\) 。對於一個在 數組\(p\) 中值為 \(i(1≤i≤n)\) 的數,我們可以讓前綴和數組右邊都減去 \(a[pos[i]]\) (\(pos[i]\) 為 \(i\) 在原數組中的位置),左邊都加上 \(a[pos[i]]\) ,也就是我們欽定他最終一定在左邊數組,即如果一開始划分在該數左面,這數肯定要移動到左邊數組,就要花費他的代價,同理如果一開始划分在該數右面,這數也不用移動了,但是本來我們是算了前綴和的,是考慮到他要移動,因此要把這個代價減去。這兩步做完以后直接查最小值最后統籌一下答案就行了。
這個什么區間加減·區間查詢 用線段樹維護一下就行了,注意下標和細節邊界條件!
時間復雜度 \(O(nlogn)\)
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef double db;
#define _for(i,a,b) for(int i = (a);i < b;i ++)
#define _rep(i,a,b) for(int i = (a);i > b;i --)
#define INF 0x3f3f3f3f3f3f3f3f
#define ZHUO 11100000007
#define SHENZHUO 1000000007
#define MIKUNUM 39
#define pb push_back
#define debug() printf("Miku Check OK!\n")
#define maxn 200003
#define X first
#define Y second
int n;
ll p[maxn];
ll a[maxn];
ll pos[maxn];
struct SegTree
{
struct SegNode
{
int l,r;
ll add;
ll min;
#define l(x) tree[x].l
#define r(x) tree[x].r
#define add(x) tree[x].add
#define MIN(x) tree[x].min
}tree[maxn<<2];
void build(int p,int l,int r)
{
l(p) = l,r(p) = r;
if(l==r)
{
MIN(p) = a[l];
return ;
}
int mid = (l+r)/2;
build(p*2, l, mid);
build(p*2+1, mid+1, r);
MIN(p) = min(MIN(p*2),MIN(p*2+1));
}
void spread(int p)
{
if(add(p))
{
MIN(p*2) += add(p);
MIN(p*2+1) += add(p);
add(p*2) += add(p);
add(p*2+1) += add(p);
add(p) = 0;
}
}
void change(int p,int l,int r,ll d)
{
if(l <= l(p) && r >= r(p))
{
MIN(p) += d;
add(p) += d;
return ;
}
spread(p);
int mid = (l(p)+r(p))/2;
if(l <= mid)
change(p*2, l, r, d);
if(r > mid)
change(p*2+1, l, r, d);
MIN(p) = min(MIN(p*2),MIN(p*2+1));
}
ll ask4min(int p,int l,int r)
{
if(l <= l(p) && r >= r(p))
return MIN(p);
spread(p);
int mid = (l(p)+r(p))/2;
ll val = INF;
if(l <= mid)
val = min(val,ask4min(p*2, l, r));
if(r > mid)
val = min(val,ask4min(p*2+1, l, r));
return val;
}
}T;
int main()
{
scanf("%d",&n);
_for(i,1,n+1)
{
scanf("%lld",&p[i]);
pos[p[i]] = i;
}
_for(i,1,n+1)
scanf("%lld",&a[i]);
T.build(1,1,n);
_for(i,1,n)
T.change(1, i+1, n, a[i]);
ll rnt = T.ask4min(1, 1, n-1);
_for(i,1,n+1)
{
if(pos[i]>1)
T.change(1, 1, pos[i]-1, a[pos[i]]);
T.change(1, pos[i], n, -a[pos[i]]);
rnt = min(rnt,T.ask4min(1, 1, n-1));
}
printf("%lld\n",rnt);
return 0;
}
F. Good Contest
數論概率論,還是卓爺的鍋,卓爺 \(tql\)
