2018北京冬令營模擬題
day1
“普及組選手做前 3 道題目,提高組選手做后 3 道題目”,所以 T1 我就不寫題解了。
售票(kartomat)
試題描述
C 市火車站最近出現了一種新式自動售票機。買票時,乘客要先在售票機上輸入終點名稱。一共有 \(N\) 處目的地,隨着乘客按順序輸入終點名稱的每個字母,候選終點站數目會逐漸減少。
在自動售票機屏幕上,有一個 \(4\) 行 \(8\) 列的鍵盤,如下圖所示。
在乘客每輸入一個字母后,鍵盤上只有有效字符是可選的(取決於還有哪些候選終點站),其余的字母會被字符 *
取代。
告訴你 \(N\) 處目的地的名稱,以及乘客已經輸入的若干字符,請你輸出鍵盤目前的狀態。
輸入
輸入文件名為 kartomat.in。
第一行為一個整數 \(N\)(\(1 \le N \le 50\))。接下來 \(N\) 行,每行一個由大寫英文字母組成的長度不超過 \(100\) 的字符串,表示一處目的地。最后一行,一個長度不超過 \(100\) 的字符串,表示按順序輸入的若干字符。
輸出
輸出文件名為 kartomat.out。
輸出 \(4\) 行,每行一個長度為 \(8\) 的字符串,表示鍵盤狀態。
輸入示例
4
ZAGREB
SISAK
ZADAR
ZABOK
ZA
輸出示例
****B*D*
*G******
********
********
數據規模及約定
見“輸入”
題解
暴力。
// 略
數字(rekons)
試題描述
有一天,Mirko 在一張紙上寫了 \(N\) 個實數,然后在另一張紙上寫下了這些實數的所有整數倍中數值在區間 \([A, B]\) 范圍內的所有數(經過去重)。
第二天,Mirko 發現找不到寫着 \(N\) 個實數的那張紙了,而只剩下另一張紙。
請你幫助 Mirko 還原原本的 \(N\) 個實數。
注意:本題有 Special Judge。
輸入
輸入文件名為 rekons.in。
第一行是一個整數 \(K\),表示剩下的那張紙上共有 \(K\) 個實數。
第二行是兩個整數 \(A\) 和 \(B\)。
接下來 \(K\) 行,每行一個實數,表示紙上的 \(K\) 個實數。實數已經去重,按遞增順序給出。所有實數至多有 \(5\) 位小數。
輸出
輸出文件名為 rekons.out。
輸出 \(N\) 行,每行一個實數,表示一組解。數據保證有解。如果有多組解,輸出 \(N\) 最小的;若還是有多組解,輸出任意一組均可。
輸入示例1
4
1 2
1
1.4
1.5
2
輸出示例1
0.5
0.7
輸入示例2
5
10 25
12
13.5
18
20.25
24
輸出示例2
6.0
6.75
數據規模及約定
\(30\%\) 的測試數據:\(K \le 12\)。
\(50\%\) 的測試數據:輸入的 \(K\) 個實數都是整數。
\(100\%\) 的測試數據:\(1 \le K \le 50\),\(1 \le A < B \le 10^6\)。
題解
直接貪心(將所有數字、兩兩數字之間的差從小到大排序然后從前往后盡量選取)能夠得到 \(90\) 分。
然而這樣不對。所以怎么辦呢?爆搜!
首先將上面貪心策略選出來的數中(令選出來的數為集合 \(S\)),如果 \(\exists a, b \in S, a|b\),那么刪掉 \(a\);再加一個最優性剪枝;最后在卡一下時間(如果搜了 \(900000\) 次就退出,直接使用當前最優解),就過了(還可以在搜之前貪心一下得到一個比較優的解,充分利用最優性剪枝)。
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cctype>
#include <algorithm>
#include <map>
using namespace std;
#define rep(i, s, t) for(int i = (s), mi = (t); i <= mi; i++)
#define dwn(i, s, t) for(int i = (s), mi = (t); i >= mi; i--)
int read() {
int x = 0, f = 1; char c = getchar();
while(!isdigit(c)){ if(c == '-') f = -1; c = getchar(); }
while(isdigit(c)){ x = x * 10 + c - '0'; c = getchar(); }
return x * f;
}
#define maxn 55
#define maxm 2560
#define LL long long
int n, cd, cu;
LL L, R, A[maxn], dt[maxm], getd[maxm], use[maxm];
map <LL, int> sol;
int ans, Time;
bool now[maxn];
LL Ans[maxn], nAns[maxn];
bool dfs(int cur, int nans) {
if(nans >= ans) return 0;
bool pass = 1;
rep(i, 1, n) pass &= now[i];
if(pass) return ans = nans, memcpy(Ans, nAns, sizeof(nAns)), 0;
if(cur > cu) {
if(++Time > 900000) return 1;
return 0;
}
bool tmp[maxn], has = 0; memcpy(tmp, now, sizeof(now));
rep(j, 1, n) if(A[j] % use[cur] == 0) has |= !now[j], now[j] = 1;
if(has) {
nAns[nans+1] = use[cur];
if(dfs(cur + 1, nans + 1)) return 1;
memcpy(now, tmp, sizeof(now));
}
if(dfs(cur + 1, nans)) return 1;
return 0;
}
int main() {
freopen("rekons.in", "r", stdin);
freopen("rekons.out", "w", stdout);
n = read();
double x; scanf("%lf", &x);
L = (LL)(x * 1e5 + .5);
scanf("%lf", &x);
R = (LL)(x * 1e5 + .5);
rep(i, 1, n) {
scanf("%lf", &x);
A[i] = (LL)(x * 1e5 + .5);
sol[A[i]] = i;
}
rep(i, 1, n){ rep(j, i + 1, n) dt[++cd] = A[j] - A[i]; dt[++cd] = A[i]; }
int cg = 0;
rep(i, 1, cd) {
LL g = dt[i], nw = (L + g - 1) / g * g; bool ok = 1, has = 0;
while(nw <= R) {
if(!sol.count(nw)){ ok = 0; break; }
if(!now[sol[nw]]) has = 1;
nw += g;
}
if(ok) getd[++cg] = g;
if(!ok || !has) continue;
nw = (L + g - 1) / g * g;
while(nw <= R) now[sol[nw]] = 1, nw += g;
Ans[++ans] = g;
}
rep(i, 1, cg) {
bool ok = 1;
rep(j, 1, i - 1) if(getd[i] % getd[j] == 0){ ok = 0; break; }
if(ok) use[++cu] = getd[i];
}
memset(now, 0, sizeof(now));
random_shuffle(use + 1, use + cu + 1);
dfs(1, 0);
rep(i, 1, ans) printf("%.5lf\n", (double)Ans[i] / 1e5);
fclose(stdin);
fclose(stdout);
return 0;
}
詞韻(rima)
試題描述
Adrian 很喜歡詩歌中的韻。他認為,兩個單詞押韻當且僅當它們的最長公共后綴的長度至少是其中較長單詞的長度減一。也就是說,單詞 \(A\) 與單詞 \(B\) 押韻當且僅當 \(LCS(A, B) \ge \mathrm{max}(|A|, |B|) – 1\)。(其中 \(LCS\) 是最長公共后綴 longest common suffix 的縮寫)
現在,Adrian 得到了 \(N\) 個單詞。他想從中選出盡可能多的單詞,要求它們能組成一個單詞序列,使得單詞序列中任何兩個相鄰單詞是押韻的。
輸入
輸入文件名為 rima.in。
第一行是一個整數 \(N\)。
接下來 \(N\) 行,每行一個由小寫英文字母組成的字符串,表示每個單詞。所有單詞互不相同。
輸出
輸出文件名為 rima.out。
輸出一行,為一個整數,表示最長單詞序列的長度。
輸入示例
5
ask
psk
k
krafna
sk
輸出示例
4
數據規模及約定
\(30\%\) 的測試數據:\(1 \le N \le 20\),所有單詞長度之和不超過 \(3000\)。
\(100\%\) 的測試數據:\(1 \le N \le 500000\),所有單詞長度之和不超過 \(3000000\)。
題解
把串反過來建一棵 trie,然后就變成在樹上找一條路徑,路徑中相鄰兩個點的 lca 與這兩個點距離最多為 \(1\),於是就變成樹形 dp 了。分兩種情況轉移:路徑一端在根節點和兩端都不在根節點。
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cctype>
#include <algorithm>
using namespace std;
#define rep(i, s, t) for(int i = (s), mi = (t); i <= mi; i++)
#define dwn(i, s, t) for(int i = (s), mi = (t); i >= mi; i--)
int read() {
int x = 0, f = 1; char c = getchar();
while(!isdigit(c)){ if(c == '-') f = -1; c = getchar(); }
while(isdigit(c)){ x = x * 10 + c - '0'; c = getchar(); }
return x * f;
}
#define maxn 3000010
#define x first
#define y second
int ToT, val[maxn], head[maxn], nxt[maxn], ch[maxn];
void Insert(char *S) {
int u = 1, n = strlen(S);
dwn(i, n - 1, 0) {
int c = S[i] - '0';
bool has = 0;
for(int e = head[u]; e; e = nxt[e]) if(ch[e] == c){ has = 1; u = e; break; }
if(!has) {
nxt[++ToT] = head[u]; ch[ToT] = c; head[u] = ToT;
u = ToT;
}
}
val[u]++;
return ;
}
int f[2][maxn];
void dp(int u) {
int cnts = 0, mx = -1, mx2 = -1;
for(int e = head[u]; e; e = nxt[e]) {
dp(e); cnts += val[e];
int nval = f[1][e] - val[e];
if(nval > mx) mx2 = mx, mx = nval;
else if(nval > mx2) mx2 = nval;
}
cnts += val[u];
if(val[u]) f[1][u] = cnts + max(mx, 0);
if(mx2 >= 0) f[0][u] = cnts + mx + mx2;
// printf("f[%d]: %d %d\n", u, f[0][u], f[1][u]);
return ;
}
int n;
char S[maxn];
int main() {
freopen("rima.in", "r", stdin);
freopen("rima.out", "w", stdout);
ToT = 1;
n = read();
rep(i, 1, n) scanf("%s", S), Insert(S);
dp(1);
int ans = 0;
rep(i, 1, ToT) ans = max(ans, max(f[0][i], f[1][i]));
printf("%d\n", ans);
fclose(stdin);
fclose(stdout);
return 0;
}
八維(osm)
試題描述
我們將一個 \(M\) 行 \(N\) 列的字符矩陣無限復制,可以得到一個無限字符矩陣。
例如,對於以下矩陣
,
可以無限復制出矩陣
。
我們認為矩陣是八連通的。八連通,指矩陣中的每個位置與上下左右和四個斜向(左上、右上、左下、右下)的位置相鄰。因此,從矩陣任意位置出發沿八個方向中的任意一個都可以無限延長。
如果我們隨機選擇一個位置和一個方向,則可以從此位置開始沿此方向連續選取 \(K\) 個字符組成一個字符串。問,兩次這樣操作得到兩個相同字符串的概率是多少。(假設隨機選擇時任意位置是等可能的,任意方向也是等可能的)
輸入
輸入文件名為 osm.in。
第一行是三個整數 \(M\),\(N\),\(K\)。
接下來 \(M\) 行,每行一個由小寫英文字母組成的長度為 \(N\) 的字符串,即 \(M \times N\) 的字符矩陣。保證矩陣中至少出現兩種不同字符。
輸出
輸出文件名為 osm.out。
輸出一行,為一個化簡后的分數,表示概率。
輸入示例1
1 2 2
ab
輸出示例1
5/16
輸入示例2
3 3 10
ban
ana
nab
輸出示例2
2/27
數據規模及約定
\(30\%\) 的測試數據:\(M, N \le 10\),\(K \le 100\)。
\(50\%\) 的測試數據:\(M = N\)。
\(100\%\) 的測試數據:\(1 \le M, N \le 500\),\(2 \le K \le 10^9\)。
題解
由於這個無限矩陣是無限復制得到的,所以算概率只需要關注以里面的 \(M \times N\) 個格子作為起點的,八個方向的 \(8MN\) 個串就可以了,然后相同的串記一個數,最后求的就是每個本質不同的串的數目的平方和。
我們可以倍增維護一個長度為 \(K\) 的串。具體來講,先枚舉一個方向 \(d\),我們維護 \(H(k, i, j)\) 表示以 \((i, j)\) 為起點,向方向 \(d\) 延伸 \(2^k\) 長度的串的 hash 值。具體怎么轉移維護請讀者思考。
開 unsigned long long 才能過,unsigned int 會有沖突。
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cctype>
#include <algorithm>
using namespace std;
#define rep(i, s, t) for(int i = (s), mi = (t); i <= mi; i++)
#define dwn(i, s, t) for(int i = (s), mi = (t); i >= mi; i--)
int read() {
int x = 0, f = 1; char c = getchar();
while(!isdigit(c)){ if(c == '-') f = -1; c = getchar(); }
while(isdigit(c)){ x = x * 10 + c - '0'; c = getchar(); }
return x * f;
}
#define maxn 500
#define maxnode 1000010
#define maxlog 30
#define UI unsigned long long
#define rate 233
#define LL long long
int n, m, K;
UI key[maxlog][maxn][maxn], rpow[maxlog];
char Map[maxn][maxn];
const int HMOD = 1000037, dirx[8] = {-1, -1, -1, 0, 0, 1, 1, 1}, diry[8] = {-1, 0, 1, -1, 1, -1, 0, 1};
struct Hash {
int ToT, head[HMOD], nxt[maxnode], val[maxnode];
UI key[maxnode];
int Find(UI x) {
int u = x % HMOD;
for(int e = head[u]; e; e = nxt[e]) if(key[e] == x) return e;
return 0;
}
void Insert(UI x) {
int tmp = Find(x);
if(tmp) return (void)(val[tmp]++);
int u = x % HMOD;
nxt[++ToT] = head[u]; val[ToT] = 1; key[ToT] = x; head[u] = ToT;
return ;
}
} hh;
LL gcd(LL a, LL b){ return b ? gcd(b, a % b) : a; }
int main() {
freopen("osm.in", "r", stdin);
freopen("osm.out", "w", stdout);
n = read(); m = read(); K = read();
rep(i, 0, n - 1) scanf("%s", Map[i]);
rep(d, 0, 7) {
rep(i, 0, n - 1) rep(j, 0, m - 1) key[0][i][j] = Map[i][j] - 'a';
rpow[0] = rate;
for(int s = 1; (1 << s) <= K; s++) {
rpow[s] = rpow[s-1] * rpow[s-1];
rep(i, 0, n - 1) rep(j, 0, m - 1) {
int x = (i + dirx[d] * (1 << s >> 1) % n + n) % n, y = (j + diry[d] * (1 << s >> 1) % m + m) % m;
key[s][i][j] = key[s-1][i][j] * rpow[s-1] + key[s-1][x][y];
}
}
rep(i, 0, n - 1) rep(j, 0, m - 1) {
int k = K, x = i, y = j; UI now = 0;
dwn(s, maxlog - 1, 0) if(k >= (1 << s)) {
now = now * rpow[s] + key[s][x][y];
k -= 1 << s;
x = (x + dirx[d] * (1 << s) % n + n) % n;
y = (y + diry[d] * (1 << s) % m + m) % m;
}
hh.Insert(now);
}
}
LL A = 0, B = (LL)n * m * 8 * n * m * 8;
rep(i, 1, hh.ToT) A += (LL)hh.val[i] * hh.val[i];
LL g = gcd(A, B); A /= g; B /= g;
printf("%lld/%lld\n", A, B);
fclose(stdin);
fclose(stdout);
return 0;
}
day2
魔法串(magic)
試題描述
小 N 最近在沉迷數學問題。
對於一個數字串 \(S\),如果可以將它划分成兩個數字 \(A\)、\(B\),滿足:
1、\(\overline{S} = \overline{AB}\)。
2、\(A\)、\(B\) 均不包含前導 \(0\)。
3、\(B\) 是 \(A\) 的倍數,且是完全立方數。
那么小 N 就認為該划分是一個“好划分”。如對於數字串 \(11297\),\((11, 297)\) 就是一個“好划分”。
如果一個數字串 \(S\) 至少有兩個“好划分”,那么小 N 就認為 \(S\) 是一個“魔法串”。如數字串 \(1335702375\) 就是一個“魔法串”,其“好划分”有 \((1,335702375)\) 和 \((133,5702375)\)。
現在給定正整數 \(N\),小 N 需要你幫她求出一個長度恰好為 \(N\) 的“魔法串”\(S\),如果無解請輸出 QwQ
。
輸入
一行一個正整數 \(N\)。
輸出
一行一個長度恰好為 \(N\) 的“魔法串”\(S\),如果無解請輸出 QwQ
。
輸入示例
19
輸出示例
1124784124392112128
數據規模及約定
對於 \(30\%\) 的數據:\(1 \le N \le 10\)。
對於 \(50\%\) 的數據:\(1 \le N \le 35\)。
對於 \(100\%\) 的數據:\(1 \le N \le 100\)。
題解
對於一個魔法串 \(S\),有 \(\overline{S000}\) 是魔法串。所以我們只需要將 \(N\) 模 \(3\) 分類,找到每一類中最短的那個就好了。
通過搜索發現當 \(N < 5\) 時無解,\(N = 5, 6, 7\) 時搜出來就好了。
搜索程序(輸入 \(N\),它會搜出長度為 \(N\) 的一個魔法串,我們只需要分別輸入 \(5, 6, 7\) 就好了):
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cctype>
#include <algorithm>
#include <cmath>
using namespace std;
#define rep(i, s, t) for(int i = (s), mi = (t); i <= mi; i++)
#define dwn(i, s, t) for(int i = (s), mi = (t); i >= mi; i++)
int read() {
int x = 0, f = 1; char c = getchar();
while(!isdigit(c)){ if(c == '-') f = -1; c = getchar(); }
while(isdigit(c)){ x = x * 10 + c - '0'; c = getchar(); }
return x * f;
}
#define maxn 10
#define LL long long
int n, A[maxn];
bool check() {
int x = 0, y, cnt = 0;
rep(i, 1, n) {
x = x * 10 + A[i];
if(!A[i+1]) continue;
y = 0;
rep(j, i + 1, n) y = y * 10 + A[j];
if(y % x == 0) {
int t = (int)pow(y / x, 1.0 / 3.0);
if(((LL)t * t * t == y / x || (LL)(t + 1) * (t + 1) * (t + 1) == y / x || (LL)(t - 1) * (t - 1) * (t - 1) == y / x)) {
// printf("here: %d %d\n", x, y);
if(++cnt == 2) return 1;
}
}
}
return 0;
}
void dfs(int cur) {
if(cur > n) {
if(check()){ rep(i, 1, n) printf("%d", A[i]); putchar('\n'); exit(0); }
return ;
}
for(A[cur] = cur == 1 ? 1 : 0; A[cur] <= 9; A[cur]++) dfs(cur + 1);
return ;
}
int main() {
n = read();
dfs(1);
return 0;
}
最終程序:
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cctype>
#include <algorithm>
#include <cmath>
using namespace std;
#define rep(i, s, t) for(int i = (s), mi = (t); i <= mi; i++)
#define dwn(i, s, t) for(int i = (s), mi = (t); i >= mi; i--)
int read() {
int x = 0, f = 1; char c = getchar();
while(!isdigit(c)){ if(c == '-') f = -1; c = getchar(); }
while(isdigit(c)){ x = x * 10 + c - '0'; c = getchar(); }
return x * f;
}
int num[3] = {324000, 1621296, 73584}, bit[3] = {6, 7, 5};
int main() {
freopen("magic.in", "r", stdin);
freopen("magic.out", "w", stdout);
int n = read();
if(n < 5) return puts("QwQ"), 0;
printf("%d", num[n%3]); rep(i, 1, (n - bit[n%3]) / 3) printf("000"); putchar('\n');
fclose(stdin);
fclose(stdout);
return 0;
}
數字統計(count)
試題描述
小 A 正在研究一些數字統計問題。有一天他突然看到了一個這樣的問題:
將 \([L..R]\) 中的所有整數用 \(M\) 位二進制數表示(允許出現前導 \(0\))。現在將這些數中的每一個作如下變換:
從這個數的最低兩位開始,如果這兩位都是 \(0\),那么 \(X=1\),否則 \(X=0\)。現在將這兩位刪去,然后將 \(X\) 放在原來最低位的位置上。重復這個變換直到這個數只剩下一位為止。
例如 \(01001\) 的變換過程如下:
\(01001 \rightarrow 0100 \rightarrow 011 \rightarrow 00 \rightarrow 1\)。
現在的問題是變換后的所有數中,值為 \(Y\)(\(Y\) 為 \(0\) 或 \(1\))的有多少個?
小 A 不會了,他想讓你幫助他完成這個問題。
輸入
輸入文件包含多組測試數據。
第一行,一個整數 \(T\),表示測試數據的組數。
接下來的 \(T\) 節,每節對應一組測試數據,格式如下:
第一行,兩個整數 \(M\)、\(Y\)。
第二行,兩個 \(M\) 位二進制數 \(L\)、\(R\)。
輸出
對於每組測試數據,輸出一行,一個二進制數,表示該組測試數據中 \([L..R]\) 中的所有整數變換后的值為 \(Y\) 的個數。這里的二進制數不允許出現前導 \(0\)。
輸入示例
1
3 1
001 101
輸出示例
11
數據規模及約定
對於 \(20\%\) 的數據:\(1 \le M \le 16\)。
對於 \(40\%\) 的數據:\(1 \le M \le 32\)。
對於 \(100\%\) 的數據:\(1 \le M \le 200\),\(1 \le T \le 50\)。
題解
由於只要兩位中有一個 \(1\) 都會變成 \(0\),所以關鍵就要看最靠左的 \(1\) 位置。
若最左的 \(1\) 的位置在奇數位(位置從 \(1\) 開始標號),那么最終會變成 \(0\),否則最終變成 \(1\)。注意特判最后一位,最后一位是 \(1\) 等價於倒數第二位是 \(1\)。
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cctype>
#include <algorithm>
#include <cmath>
using namespace std;
#define rep(i, s, t) for(int i = (s), mi = (t); i <= mi; i++)
#define dwn(i, s, t) for(int i = (s), mi = (t); i >= mi; i--)
int read() {
int x = 0, f = 1; char c = getchar();
while(!isdigit(c)){ if(c == '-') f = -1; c = getchar(); }
while(isdigit(c)){ x = x * 10 + c - '0'; c = getchar(); }
return x * f;
}
#define maxn 210
int n, L[maxn], R[maxn], tmp[maxn], tt[maxn], ans[maxn];
char Lc[maxn], Rc[maxn];
void Plus(int *a, int *b) {
rep(i, 1, n) a[i] += b[i];
dwn(i, n, 1) a[i-1] += a[i] >> 1, a[i] &= 1;
return ;
}
void Minus(int *ans, int *a, int *b) {
rep(i, 1, n) ans[i] = a[i] - b[i];
dwn(i, n, 1) {
if(ans[i] < 0) ans[i-1] -= 1 - ans[i] >> 1;
ans[i] = abs(ans[i]) & 1;
}
return ;
}
void work() {
n = read(); int Y = read();
scanf("%s%s", Lc + 1, Rc + 1);
int st = 1, lst = 1; while(st <= n && Rc[st] == '0') st++;
rep(i, 1, n) L[i] = Lc[i] - '0', R[i] = Rc[i] - '0';
while(lst <= n && !L[lst]) lst++;
memset(ans, 0, sizeof(ans));
if(!Y) {
if(R[st] == L[st] && !(st & 1)) return (void)puts("0");
if(R[st] == L[st]) {
Minus(ans, R, L);
memset(tt, 0, sizeof(tt));
tt[n] = 1;
Plus(ans, tt);
}
else {
for(int i = 1; i <= lst; i += 2) if(st <= i) {
if(i == n && !(n - 1 & 1)) break;
if(i == st) {
R[st] = 0;
// printf("1+ "); rep(j, 1, n) printf("%d", R[j]); putchar('\n');
R[st] = 0; Plus(ans, R); R[st] = 1;
memset(tt, 0, sizeof(tt)); tt[n] = 1;
Plus(ans, tt);
}
else if(i == lst) {
memset(tt, 0, sizeof(tt));
tt[i] = 1;
L[i] = 0;
Minus(tmp, tt, L);
L[i] = 1;
// printf("2+ "); rep(j, 1, n) printf("%d", tmp[j]); putchar('\n');
Plus(ans, tmp);
}
else {
memset(tt, 0, sizeof(tt));
tt[i] = 1;
// printf("3+ "); rep(j, 1, n) printf("%d", tt[j]); putchar('\n');
Plus(ans, tt);
}
if(n - i <= 2 && i < n) i = n - 2;
}
}
}
else {
if(R[st] == L[st] && (st & 1)) return (void)puts("0");
if(R[st] == L[st]) {
Minus(ans, R, L);
memset(tt, 0, sizeof(tt));
tt[n] = 1;
Plus(ans, tt);
}
else {
for(int i = 2; i <= lst; i += 2) if(st <= i) {
if(i == n && (n - 1 & 1)) break;
if(i == st) {
R[st] = 0;
// printf("1+ "); rep(j, 1, n) printf("%d", R[j]); putchar('\n');
R[st] = 0; Plus(ans, R); R[st] = 1;
memset(tt, 0, sizeof(tt)); tt[n] = 1;
Plus(ans, tt);
}
else if(i == lst) {
memset(tt, 0, sizeof(tt));
tt[i] = 1;
L[i] = 0;
Minus(tmp, tt, L);
L[i] = 1;
// printf("2+ "); rep(j, 1, n) printf("%d", tmp[j]); putchar('\n');
Plus(ans, tmp);
}
else {
memset(tt, 0, sizeof(tt));
tt[i] = 1;
// printf("3+ "); rep(j, 1, n) printf("%d", tt[j]); putchar('\n');
Plus(ans, tt);
}
if(n - i <= 2 && i < n) i = n - 2;
}
}
}
st = 1;
while(st <= n && !ans[st]) st++;
if(st > n) puts("0");
else { for(; st <= n; st++) printf("%d", ans[st]); putchar('\n'); }
return ;
}
int main() {
freopen("count.in", "r", stdin);
freopen("count.out", "w", stdout);
int T = read();
while(T--) work();
fclose(stdin);
fclose(stdout);
return 0;
}
基礎匹配算法練習題(match)
試題描述
小 S 最近學會了二分圖匈牙利匹配算法。
現在二分圖的 \(X\) 部有 \(N\) 個數字 \(A_i\),\(Y\) 部有 \(K\) 個數字 \(C_i\)。
已知如果 \(A_i + C_j \le Z\),那么 \(A_i\) 和 \(C_j\) 之間就有一條邊,求二分圖 \((X, E, Y)\) 的最大匹配數。
小 S 是初學者,所以她想做多做一些練習來鞏固知識。於是她找到了一個長度為 \(M\) 的正整數數組 \(B[]\),每次她會在 \(B[]\) 數組中抽取一段連續的區間 \([L_i,R_i]\),把區間 \([L_i,R_i]\) 的所有數字作為二分圖 \(Y\) 部的 \(K\) 個數字 \(C_i\),然后重新求一次二分圖最大匹配數。
小 S 打算一共做 \(Q\) 次練習,但是她不知道每次計算出的答案對不對,你能幫幫她嗎?
輸入
第一行為三個正整數 \(N\)、\(M\)、\(Z\)。
第二行為 \(N\) 個正整數,第 \(i\) 個正整數表示 \(A_i\)。
第三行為 \(M\) 個正整數,第 \(i\) 個正整數表示 \(B_i\)。
接下來 \(Q\) 行每行兩個正整數,第 \(i\) 行的兩個正整數分別表示 \(L_i\)、\(R_i\)。
輸出
對於每次練習,輸出該次練習的答案。
輸入示例
4 10 8
1 2 4 6
6 3 6 2 8 4 9 10 6 8
4
1 4
2 5
5 6
1 6
輸出示例
4
3
1
4
數據規模及約定
測試數據編號 | $N$ | $M$ | $Q$ |
---|---|---|---|
1 - 4 | $\le 50$ | $\le 50$ | $\le 50$ |
5 - 10 | $\le 2501$ | $\le 2501$ | $\le 2501$ |
11 - 14 | $\le 152501$ | $\le 45678$ | $\le 45678$ |
15 - 16 | $\le 152501$ | $\le 50$ | $\le 52501$ |
17 - 20 | $\le 152501$ | $\le 52501$ | $\le 52501$ |
對於 \(100\%\) 的數據:\(1 \le A_i, B_i, Z \le 10^9\),\(1 \le L_i \le R_i \le Q\)。
保證數據有一定梯度。
題解
這是一個假的二分圖匹配,可以貪心搞。
考慮將 \(A\) 從小到大排序,然后對於 \(B_i\) 求出 \(f_i\) 使得 \(A_{f_i} + B_i \le Z\) 且 \(f_i\) 最大。那么對於一個區間內的 \(f_i\),我們在 \(A\) 數組對應位置上打上標記,然后從右往左掃,每往左一格就減一,遇到一個標記就加上這個標記,然后最后剩下的數字就是 \(B\) 中沒有匹配上的。
那么 \(R_q - L_q + 1 - \max \{ S_i - i \}\) 就是答案了,其中 \(S_i\) 表示位置 \(i\) 的標記的前綴和。
這樣的話莫隊 + 線段樹暴力做。
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cctype>
#include <algorithm>
#include <cmath>
using namespace std;
#define rep(i, s, t) for(int i = (s), mi = (t); i <= mi; i++)
#define dwn(i, s, t) for(int i = (s), mi = (t); i >= mi; i--)
int read() {
int x = 0, f = 1; char c = getchar();
while(!isdigit(c)){ if(c == '-') f = -1; c = getchar(); }
while(isdigit(c)){ x = x * 10 + c - '0'; c = getchar(); }
return x * f;
}
#define maxn 152510
#define maxm 52510
int n, m, Z, A[maxn], B[maxm], num[maxm], bl[maxm], Ans[maxm];
struct Que {
int l, r, id;
Que() {}
Que(int _1, int _2, int _3): l(_1), r(_2), id(_3) {}
bool operator < (const Que& t) const {
if(bl[l] != bl[t.l]) return bl[l] < bl[t.l];
if(bl[l] & 1) return r < t.r;
else return r > t.r;
}
} qs[maxm];
int mxv[maxn<<2], addv[maxn<<2];
void build(int o, int l, int r) {
if(l == r) mxv[o] = -num[l];
else {
int mid = l + r >> 1, lc = o << 1, rc = lc | 1;
build(lc, l, mid); build(rc, mid + 1, r);
mxv[o] = max(mxv[lc], mxv[rc]);
}
return ;
}
void pushdown(int o, int l, int r) {
if(!addv[o] || l == r) return (void)(addv[o] = 0);
int lc = o << 1, rc = lc | 1;
addv[lc] += addv[o]; mxv[lc] += addv[o];
addv[rc] += addv[o]; mxv[rc] += addv[o];
addv[o] = 0;
return ;
}
void update(int o, int l, int r, int ql, int v) {
pushdown(o, l, r);
if(ql <= l) addv[o] += v, mxv[o] += v;
else {
int mid = l + r >> 1, lc = o << 1, rc = lc | 1;
if(ql <= mid) update(lc, l, mid, ql, v);
update(rc, mid + 1, r, ql, v);
mxv[o] = max(mxv[lc], mxv[rc]);
}
return ;
}
int main() {
freopen("match.in", "r", stdin);
freopen("match.out", "w", stdout);
n = read(); m = read(); Z = read();
rep(i, 1, n) A[i] = read(); sort(A + 1, A + n + 1);
int bsiz = (int)sqrt((double)m);
rep(i, 1, m) {
int v = read();
num[i] = B[i] = upper_bound(A + 1, A + n + 1, Z - v) - A - 1;
bl[i] = (i - 1) / bsiz + 1;
}
sort(num + 1, num + m + 1);
int cm = unique(num + 1, num + m + 1) - num - 1;
rep(i, 1, m) B[i] = lower_bound(num + 1, num + cm + 1, B[i]) - num;
int q = read();
rep(i, 1, q) {
int l = read(), r = read();
qs[i] = Que(l, r, i);
}
sort(qs + 1, qs + q + 1);
build(1, 0, cm);
int nl = 1, nr = 1; update(1, 0, cm, B[1], 1);
rep(i, 1, q) {
int ql = qs[i].l, qr = qs[i].r;
while(nl < ql) update(1, 0, cm, B[nl], -1), nl++;
while(nl > ql) update(1, 0, cm, B[nl-1], 1), nl--;
while(nr < qr) update(1, 0, cm, B[nr+1], 1), nr++;
while(nr > qr) update(1, 0, cm, B[nr], -1), nr--;
Ans[qs[i].id] = qr - ql + 1 - mxv[1];
}
rep(i, 1, q) printf("%d\n", Ans[i]);
fclose(stdin);
fclose(stdout);
return 0;
}
上學路線(path)
試題描述
小 B 所在的城市的道路構成了一個方形網格,它的西南角為 \((0,0)\),東北角為 \((N,M)\)。
小 B 家住在西南角,學校在東北角。現在有 \(T\) 個路口進行施工,小 B 不能通過這些路口。小 B 喜歡走最短的路徑到達目的地,因此他每天上學時都只會向東或北行走;而小 B 又喜歡走不同的路徑,因此他問你按照他走最短路徑的規則,他可以選擇的不同的上學路線有多少條。由於答案可能很大,所以小 B 只需要讓你求出路徑數 \(\mod P\) 的值。
輸入
第一行為四個整數 \(N\)、\(M\)、\(T\)、\(P\)。
接下來的 \(T\) 行,每行兩個整數,表示施工的路口的坐標。
輸出
一行一個整數,表示路徑數 \(\mod P\) 的值。
輸入示例
3 4 3 1019663265
3 0
1 1
2 2
輸出示例
8
數據規模及約定
測試點編號 | $N$、$M$ 的范圍 | $T$ 的范圍 | $P$ 的范圍 |
---|---|---|---|
1 - 2 | $1 \le N, M \le 1000$ | $0 \le T \le 200$ | $P = 1019663265$ |
3 - 4 | $1 \le N, M \le 100000$ | $T = 0$ | $P = 1000003$ |
5 - 6 | $1 \le N, M \le 10^9$ | $P = 1019663265$ | |
7 - 8 | $1 \le N, M \le 100000$ | $0 \le T \le 200$ | $P = 1000003$ |
9 - 10 | $1 \le N, M \le 10^9$ | $P = 1019663265$ |
對於 \(100\%\) 的數據:\(1 \le A_i, B_i, Z \le 10^9\),\(1 \le L_i \le R_i \le Q\)。
保證數據有一定梯度。
題解
考慮容斥原理,令 \(f(i)\) 表示從起點走到 \(i\) 並且不經過 \(i\) 左下角任何點的方案數。我們可以先暴力從起點走到 \(i\),不考慮限制,然后減掉“第一次”出現錯誤的地方分別在每個點的方案數,形式化地:
對於 \(P = 1000003\) 的數據直接做,預處理一下階乘和階乘逆元;由於 \(1019663265 = 3 \times 5 \times 6793 \times 10007\),那么用 lucas 定理,然后中國剩余定理合並即可。
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cctype>
#include <algorithm>
#include <cmath>
using namespace std;
#define rep(i, s, t) for(int i = (s), mi = (t); i <= mi; i++)
#define dwn(i, s, t) for(int i = (s), mi = (t); i >= mi; i--)
#define LL long long
LL read() {
LL x = 0, f = 1; char c = getchar();
while(!isdigit(c)){ if(c == '-') f = -1; c = getchar(); }
while(isdigit(c)){ x = x * 10 + c - '0'; c = getchar(); }
return x * f;
}
#define maxn 200010
#define maxg 1010
#define maxt 210
LL n, m;
int ct, MOD;
#define pll pair <LL, LL>
#define x first
#define y second
#define mp(x, y) make_pair(x, y)
pll ps[maxt];
int fac[maxn], ifac[maxn], F[maxt][maxt], Ans[maxt];
LL C(int n, int m) {
return (LL)fac[n] * ifac[m] % MOD * ifac[n-m] % MOD;
}
void solve1() {
rep(i, 1, ct) {
int X = read(), Y = read();
ps[i] = mp(X, Y);
}
fac[0] = ifac[0] = 1;
ifac[1] = 1;
rep(i, 2, n + m) ifac[i] = (LL)(MOD - MOD / i) * ifac[MOD%i] % MOD;
rep(i, 1, n + m) ifac[i] = (LL)ifac[i] * ifac[i-1] % MOD, fac[i] = (LL)fac[i-1] * i % MOD;
ps[++ct] = mp(n, m);
sort(ps + 1, ps + ct + 1);
rep(t, 1, ct) {
Ans[t] = C(ps[t].x + ps[t].y, ps[t].x);
rep(mid, 1, t - 1) if(ps[mid].x <= ps[t].x && ps[mid].y <= ps[t].y) {
int tmp = C((ps[t].x - ps[mid].x) + (ps[t].y - ps[mid].y), ps[t].x - ps[mid].x);
tmp = (LL)tmp * Ans[mid] % MOD;
Ans[t] = (Ans[t] - tmp + MOD) % MOD;
}
}
printf("%d\n", Ans[ct]);
return ;
}
void gcd(LL a, LL b, LL& x, LL& y) {
if(!b){ x = 1; y = 0; return ; }
gcd(b, a % b, y, x); y -= a / b * x;
return ;
}
const int need = 10017;
int Fac[4][need], iFac[4][need], prime[4] = {3, 5, 6793, 10007};
LL Cm(int n, int m, int Mid) {
return (LL)Fac[Mid][n] * iFac[Mid][m] % prime[Mid] * iFac[Mid][n-m] % prime[Mid];
}
LL calc(LL n, LL m, int Mid) {
if(n < m) return 0;
if(n < prime[Mid] && m < prime[Mid]) return Cm(n, m, Mid);
return (LL)calc(n / prime[Mid], m / prime[Mid], Mid) * calc(n % prime[Mid], m % prime[Mid], Mid) % prime[Mid];
}
int Ch(LL n, LL m) {
int a1 = calc(n, m, 0), a2 = calc(n, m, 1), a3 = calc(n, m, 2), a4 = calc(n, m, 3);
LL x, y; gcd(3ll, 5ll, x, y); x *= a1 - a2; x = (x % 15 + 15) % 15; a1 = ((a1 - x * 3) % 15 + 15) % 15; a2 = a3;
gcd(15ll, 6793ll, x, y); x *= a1 - a2; x = (x % 101895 + 101895) % 101895; a1 = ((a1 - x * 15) % 101895 + 101895) % 101895; a2 = a4;
gcd(101895ll, 10007ll, x, y); x *= a1 - a2; x = (x % MOD + MOD) % MOD; a1 = ((a1 - x * 101895) % MOD + MOD) % MOD;
return a1;
}
void solve2() {
rep(i, 1, ct) {
int X = read(), Y = read();
ps[i] = mp(X, Y);
}
rep(pr, 0, 3) {
Fac[pr][0] = iFac[pr][0] = 1;
iFac[pr][1] = 1;
rep(i, 2, prime[pr] - 1) iFac[pr][i] = (LL)(prime[pr] - prime[pr] / i) * iFac[pr][prime[pr]%i] % prime[pr];
rep(i, 1, prime[pr] - 1) iFac[pr][i] = (LL)iFac[pr][i] * iFac[pr][i-1] % MOD, Fac[pr][i] = (LL)Fac[pr][i-1] * i % MOD;
}
ps[++ct] = mp(n, m);
sort(ps + 1, ps + ct + 1);
rep(t, 1, ct) {
Ans[t] = Ch(ps[t].x + ps[t].y, ps[t].x);
rep(mid, 1, t - 1) if(ps[mid].x <= ps[t].x && ps[mid].y <= ps[t].y) {
int tmp = Ch((ps[t].x - ps[mid].x) + (ps[t].y - ps[mid].y), ps[t].x - ps[mid].x);
tmp = (LL)tmp * Ans[mid] % MOD;
Ans[t] = (Ans[t] - tmp + MOD) % MOD;
}
}
printf("%d\n", Ans[ct]);
return ;
}
int main() {
freopen("path.in", "r", stdin);
freopen("path.out", "w", stdout);
n = read(); m = read(); ct = read(); MOD = read();
if(MOD == 1000003) solve1();
else solve2(); // MOD = 3 * 5 * 6793 * 10007
fclose(stdin);
fclose(stdout);
return 0;
}
day3
第 k 大斜率(slope)
試題描述
在平面直角坐標系上,有 \(n\) 個不同的點。任意兩個不同的點確定了一條直線。請求出所有斜率存在的直線按斜率從大到小排序后,第 \(k\) 條直線的斜率為多少。
為了避免精度誤差,請輸出斜率向下取整后的結果。(例如:\(\lfloor 1.5 \rfloor = 1\) ,\(\lfloor −1.5 \rfloor = −2\))
輸入
第一行,包含兩個正整數 \(n\) 和 \(k\) 。
接下來 \(n\) 行,每行包含兩個整數 \(x_i, y_i\),表示每個點的橫縱坐標。
輸出
輸出一行,包含一個整數,表示第 \(k\) 小的斜率向下取整的結果。
輸入示例
4 1
-1 -1
2 1
3 3
1 4
輸出示例
2
數據規模及約定
令 $M = 所有斜率存在且大於 0 的直線的數量 $。
對於 \(10\%\) 的數據,\(1 \le n \le 10\)。
對於 \(20\%\) 的數據,\(1 \le n \le 100\) ,\(|x_i|, |y_i| \le 10^3\)。
對於 \(30\%\) 的數據,\(1 \le n \le 1000\)。
對於 \(40\%\) 的數據,\(1 \le n \le 5000\)。
對於另 \(20\%\) 的數據,滿足 \(k = 1\)。
對於另 \(20\%\) 的數據,滿足 \(1 \le x_i, y_i \le 10^3\) 。
對於 \(100\%\) 的數據,\(1 \le n \le 100000\),\(k \le M\) ,\(|x_i|, |y_i| \le 10^8\)。
題解
二分答案 \(k\),問題轉化成了求斜率 \(\ge k\) 的直線有多少條。
如果一個過 \((x_0, y_0), (x_1, y_1)\) 的直線滿足斜率 \(\ge k\),那么就是要滿足
如果我們令 \(x_1 > x_0\),那么可以移項得到
於是我們將所有點按照橫坐標從大到小排序(若橫坐標相等則要縱坐標從小到大,這樣能夠避免對無窮大斜率的直線的統計),然后按順序查詢點 \(i\) 和后面的哪些點 \(j(j > i)\) 能夠滿足 \(y_i - kx_i \le y_j - kx_j\),將這樣的 \(j\) 的個數累加,插入點 \(i\),就可以完成斜率 \(\ge k\) 的直線的數目統計了。
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cctype>
#include <algorithm>
using namespace std;
#define rep(i, s, t) for(int i = (s), mi = (t); i <= mi; i++)
#define dwn(i, s, t) for(int i = (s), mi = (t); i >= mi; i--)
#define LL long long
const int BufferSize = 1 << 16;
char buffer[BufferSize], *Head, *Tail;
inline char Getchar() {
if(Head == Tail) {
int l = fread(buffer, 1, BufferSize, stdin);
Tail = (Head = buffer) + l;
}
return *Head++;
}
LL read() {
LL x = 0, f = 1; char c = Getchar();
while(!isdigit(c)){ if(c == '-') f = -1; c = Getchar(); }
while(isdigit(c)){ x = x * 10 + c - '0'; c = Getchar(); }
return x * f;
}
#define maxn 100010
struct Point {
int x, y;
Point() {}
Point(int _, int __): x(_), y(__) {}
bool operator < (const Point& t) const { return x != t.x ? x < t.x : y > t.y; }
} ps[maxn];
int n;
LL K;
int sumv[maxn<<2];
void update(int o, int l, int r, int p) {
if(l == r) sumv[o]++;
else {
int mid = l + r >> 1, lc = o << 1, rc = lc | 1;
if(p <= mid) update(lc, l, mid, p);
else update(rc, mid + 1, r, p);
sumv[o] = sumv[lc] + sumv[rc];
}
return ;
}
int query(int o, int l, int r, int ql) {
if(ql <= l) return sumv[o];
int mid = l + r >> 1, lc = o << 1, rc = lc | 1, ans = query(rc, mid + 1, r, ql);
if(ql <= mid) ans += query(lc, l, mid, ql);
return ans;
}
LL val[maxn], num[maxn];
LL total(int k) {
rep(i, 1, n) num[i] = val[i] = (LL)ps[i].y - (LL)k * ps[i].x;
sort(num + 1, num + n + 1);
rep(i, 1, n) val[i] = lower_bound(num + 1, num + n + 1, val[i]) - num;
memset(sumv, 0, sizeof(sumv));
LL ans = 0;
dwn(i, n, 1) {
ans += query(1, 1, n, val[i]);
update(1, 1, n, val[i]);
}
// printf("total(%d) = %lld\n", k, ans);
return ans;
}
int main() {
freopen("slope.in", "r", stdin);
freopen("slope.out", "w", stdout);
n = read(); K = read();
rep(i, 1, n) {
int x = read(), y = read();
ps[i] = Point(x, y);
}
sort(ps + 1, ps + n + 1);
int l = -(int)2e8, r = (int)2e8 + 1;
while(r - l > 1) {
int mid = l + r >> 1;
if(total(mid) < K) r = mid; else l = mid;
}
printf("%d\n", l);
fclose(stdin);
fclose(stdout);
return 0;
}
餐巾計划問題(napkin)
試題描述
一個餐廳在相繼的 \(n\) 天里,每天需用的餐巾數不盡相同。假設第 \(i\) 天(\(i = 1,2, \cdots , n\)) 需要 \(r_i\) 塊餐巾。餐廳可以在任意時刻購買新的餐巾,每塊餐巾的費用為 \(p\) 。使用過的舊餐巾,則需要經過清洗才能重新使用。把一塊舊餐巾送到清洗店 A,需要等待 \(m_1\) 天后才能拿到新餐巾,其費用為 \(c_1\) ;把一塊舊餐巾送到清洗店 B,需要等待 \(m_2\) 天后才能拿到新餐巾,其費用為 \(c_2\) 。例如,將一塊第 \(k\) 天使用過的餐巾送到清洗店 A 清洗,則可以在第 \(k + m_1\) 天使用。
請為餐廳合理地安排好 \(n\) 天中餐巾使用計划,使總的花費最小。
輸入
第一行,包含六個個正整數 \(n, m_1, m_2, c_1, c_2, p\)。
接下來輸入 \(n\) 行,每行包含一個正整數 \(r_i\) 。
輸出
輸出一行,包含一個正整數,表示最小的總花費。
輸入示例
4 1 2 2 1 3
8
2
1
6
輸出示例
35
數據規模及約定
對於 \(30\%\) 的數據,\(1 \le n \le 5\) ,\(1 \le c_1, c_2, p \le 5\) ,\(1 \le r_i \le 5\)。
對於 \(50\%\) 的數據,\(1 \le n \le 100\),\(1 \le r_i \le 50\)。
對於 \(70\%\) 的數據,\(1 \le n \le 5000\)。
對於 \(100\%\) 的數據,\(1 \le n \le 200000\),\(1 \le m_1, m_2 \le n\) ,\(1 \le c_1, c_2, p \le 100\),\(1 \le r_i \le 100\)。
題解
這題寫費用流就輸了……
我們假設已經知道了使用的餐巾數量 \(x\),那么這 \(n\) 天的使用情況就可以貪心了。首先優先使用新買的餐巾,然后優先使用慢洗能來得及的餐巾,接着優先使用日期靠后的快洗來得及的餐巾。
考慮費用流暴力增廣的過程,在滿足最大流(即滿足購買的餐巾數量足夠)的情況下,每次增加 \(1\) 的流量(即多買 \(1\) 塊餐巾),最短路都會變長。開始時最短路是負數,到一定時候最短路會變正,所以我們發現最后費用關於餐巾數量 \(x\) 的函數 \(f(x)\) 是一個下凸函數。
於是我們就可以在餐巾足夠的范圍三分 \(x\) 啦。
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cctype>
#include <algorithm>
#include <queue>
using namespace std;
#define rep(i, s, t) for(int i = (s), mi = (t); i <= mi; i++)
#define dwn(i, s, t) for(int i = (s), mi = (t); i >= mi; i--)
int read() {
int x = 0, f = 1; char c = getchar();
while(!isdigit(c)){ if(c == '-') f = -1; c = getchar(); }
while(isdigit(c)){ x = x * 10 + c - '0'; c = getchar(); }
return x * f;
}
#define maxn 200010
#define oo 2147483647
#define pii pair <int, int>
#define x first
#define y second
#define mp(x, y) make_pair(x, y)
int n, m1, m2, c1, c2, p, need[maxn], Need[maxn], sum;
deque <pii > Fast, Slow, Not;
void PushBack(deque <pii >& Q, pii x) {
if(!Q.empty() && Q.back().y == x.y) {
pii t = Q.back(); Q.pop_back();
t.x += x.x; Q.push_back(t);
}
else Q.push_back(x);
return ;
}
void PushFront(deque <pii >& Q, pii x) {
if(!Q.empty() && Q.front().y == x.y) {
pii t = Q.front(); Q.pop_front();
t.x += x.x; Q.push_front(t);
}
else Q.push_front(x);
return ;
}
int calc(int x) {
rep(i, 1, n) need[i] = Need[i];
while(!Fast.empty()) Fast.pop_back();
while(!Slow.empty()) Slow.pop_back();
while(!Not.empty()) Not.pop_back();
int ans = p * x;
rep(i, 1, n) {
while(!Not.empty() && i - Not.front().y >= m1) PushBack(Fast, Not.front()), Not.pop_front();
while(m2 < oo && !Fast.empty() && i - Fast.front().y >= m2) PushBack(Slow, Fast.front()), Fast.pop_front();
if(x >= need[i]) PushBack(Not, mp(need[i], i)), x -= need[i];
else {
if(x) PushBack(Not, mp(x, i)), need[i] -= x, x = 0;
if(m2 < oo) {
while(need[i] > 0 && !Slow.empty()) {
pii now = Slow.front(); Slow.pop_front();
if(now.x >= need[i]) PushBack(Not, mp(need[i], i)), now.x -= need[i], ans += need[i] * c2, need[i] = 0, PushFront(Slow, now);
else PushBack(Not, mp(now.x, i)), need[i] -= now.x, ans += now.x * c2;
}
}
while(need[i] > 0 && !Fast.empty()) {
pii now = Fast.back(); Fast.pop_back();
if(now.x >= need[i]) PushBack(Not, mp(need[i], i)), now.x -= need[i], ans += need[i] * c1, need[i] = 0, PushBack(Fast, now);
else PushBack(Not, mp(now.x, i)), need[i] -= now.x, ans += now.x * c1;
}
if(need[i] > 0) return oo;
}
}
return ans;
}
int main() {
freopen("napkin.in", "r", stdin);
freopen("napkin.out", "w", stdout);
n = read(); m1 = read(); m2 = read(); c1 = read(); c2 = read(); p = read();
rep(i, 1, n) Need[i] = need[i] = read(), sum += need[i];
// 1: faster; 2: slower.
if(m1 > m2) swap(m1, m2), swap(c1, c2);
if(c1 < c2) m2 = c2 = oo;
int l = 1, r = sum;
while(l < r) {
int mid = l + r >> 1;
if(calc(mid) == oo) l = mid + 1; else r = mid;
}
// printf("st: %d %d\n", l, sum);
r = sum;
while(r - l > 1) {
int ml = l + (r - l) / 3, mr = ml + r >> 1;
// printf("[%d, %d] %d %d: %d %d\n", l, r, ml, mr, calc(ml), calc(mr));
if(calc(ml) < calc(mr)) r = mr;
else l = ml + 1;
}
printf("%d\n", min(calc(l), calc(r)));
fclose(stdin);
fclose(stdout);
return 0;
}
A
試題描述
綠意盎然的一天,Scape去XX賽區加冕為王。
Scape倒開題目,看到了這樣一道題:
有一個序列 \(A\) 和 \(L,R\),每次可以合並相鄰的 \(K\) 個元素(要求 \(L \le K \le R\) ),代價為這 \(K\) 個元素的和並合並產生一個新元素,權值為這 \(K\) 個元素的和。
求把整個序列合並為一個元素的最小代價。
\(T\) 組數據,\(T \le 10, n \le 300\)。
Scape 想都不想就寫了一個 \(n^4\) 暴力,結果居然 \(T\) 了,作為 XX 之王的 Scape 自然不會管這種辣雞題,請你寫出這道題。
輸入
第一行一個整數 \(T(T \le 10)\),表示數據組數。
每組數據第一行三個整數 \(n,L,R\) 表示序列 \(A\) 的長度,和 \(K\) 的上下界限制。
第二行 \(n\) 個整數表示序列 \(A\)。
輸出
每行一個整數表示結果,無解輸出 \(0\)。
輸入示例
3
3 2 2
1 2 3
3 2 3
1 2 3
4 3 3
1 2 3 4
輸出示例
9
6
0
數據規模及約定
對於 \(20\%\) 的數據,\(n \le 20\)。
對於 \(60\%\) 的數據,\(n \le 50\)。
對於 \(100\%\) 的數據,\(n \le 300, A_i \le 100\)。
題解
還是倍增。
令 \(f(k, l, r)\) 表示對於 \([l, r]\) 中的所有數字,最后一步合並是 \(2^k\) 段合並在一起,其余的合並都是段數在 \([L, R]\) 之間的,最小代價。
\(g(k, l, r)\) 表示對於 \([l, r]\) 中的所有數字,最后一步合並為不超過 \(2^k\) 段合並在一起,其余的合並都是段數在 \([L, R]\) 之間的,最小代價。
那么這樣就可以用 \(f(k, l, r)\) 組出 \(L\),然后再用 \(g(k, l, r)\) 組出 \(R - L\),就可以合並了。
為了方便合並,我們需要維護一個 \(F(k, l, r)\)。令 \(L\) 的二進制最低的 \(k\) 位組成的數位 \(x\),那么 \(F(k, l, r)\) 就是最后一步合並是 \(x\) 段合並在一起,其余的合並都是段數在 \([L, R]\) 之間的,最小代價。
同理,再維護一個 \(G(k, l, r)\)。令 \(R - L\) 的二進制最低的 \(k\) 位組成的數位 \(x\),那么 \(G(k, l, r)\) 就是最后一步合並為不超過 \(x\) 段合並在一起,其余的合並都是段數在 \([L, R]\) 之間的,最小代價。
這樣將 \(F(maxb, l, r)\) 與 \(G(maxb, l, r)\) 的 dp 值再合並一下,就能得到區間 \([l, r]\) 的答案了。(\(maxb\) 表示二進制位數)
上面的合並都是指枚舉中轉點 \(i\),然后用 \(f_l(l, i) + f_r(i+1, r)\) 去更新 \(f_t(l, r)\),其中 \(f_l, f_r\) 指的是待合並的兩個 dp 數組,\(f_t\) 指的是合並后的目標數組。
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cctype>
#include <algorithm>
#include <cassert>
using namespace std;
#define rep(i, s, t) for(int i = (s), mi = (t); i <= mi; i++)
#define dwn(i, s, t) for(int i = (s), mi = (t); i >= mi; i--)
//#define DEBUG 233
int read() {
int x = 0, f = 1; char c = getchar();
while(!isdigit(c)){ if(c == '-') f = -1; c = getchar(); }
while(isdigit(c)){ x = x * 10 + c - '0'; c = getchar(); }
return x * f;
}
#define maxn 310
#define maxlog 9
#define oo 2147483647
int n, L, R, S[maxn];
void upd(int& a, int b) {
if(a > b) a = b;
return ;
}
int f[maxlog][maxn][maxn], g[maxlog][maxn][maxn], F[maxlog][maxn][maxn], G[maxlog][maxn][maxn], vis[maxn][maxn];
void dp(int l, int r) {
if(l > r || vis[l][r]) return ;
vis[l][r] = 1;
if(l == r) {
f[0][l][r] = g[0][l][r] = 0;
if(L & 1) F[0][l][r] = 0;
if(R - L & 1) G[0][l][r] = 0;
rep(k, 1, maxlog - 1) {
upd(g[k][l][r], g[k-1][l][r]);
if(R - L >> k & 1) G[k][l][r] = 0;
upd(G[k][l][r], G[k-1][l][r]);
if(!(L >> k & 1)) upd(F[k][l][r], F[k-1][l][r]);
}
#ifdef DEBUG
rep(k, 0, maxlog - 1) printf("f[%d][%d][%d] = %d\n", k, l, r, f[k][l][r]);
#endif
return ;
}
rep(i, l, r) dp(l, i), dp(i + 1, r);
rep(k, 1, maxlog - 1) {
rep(i, l, r) {
if(i == r) assert(f[k-1][i+1][r] == oo);
if(f[k-1][l][i] < oo && f[k-1][i+1][r] < oo) upd(f[k][l][r], f[k-1][l][i] + f[k-1][i+1][r]);
if(g[k-1][l][i] < oo && g[k-1][i+1][r] < oo) upd(g[k][l][r], g[k-1][l][i] + g[k-1][i+1][r]);
if(L >> k & 1) {
if(f[k][l][i] < oo && F[k-1][i+1][r] < oo) upd(F[k][l][r], f[k][l][i] + F[k-1][i+1][r]);
}
else upd(F[k][l][r], F[k-1][l][r]);
if(R - L >> k & 1) {
if(g[k][l][i] < oo && G[k-1][i+1][r] < oo) upd(G[k][l][r], g[k][l][i] + G[k-1][i+1][r]);
}
upd(G[k][l][r], G[k-1][l][r]);
}
if((L & -L) == (1 << k)) upd(F[k][l][r], f[k][l][r]);
// printf("F[%d][%d][%d] = %d\n", k, l, r, F[k][l][r]);
}
rep(i, l, r) if(F[maxlog-1][l][i] < oo && G[maxlog-1][i+1][r] < oo)
upd(f[0][l][r], F[maxlog-1][l][i] + G[maxlog-1][i+1][r] + S[r] - S[l-1]);
upd(g[0][l][r], f[0][l][r]);
if(L & 1) F[0][l][r] = f[0][l][r];
if(R - L & 1) G[0][l][r] = g[0][l][r];
rep(k, 1, maxlog - 1) {
upd(g[k][l][r], g[k-1][l][r]);
if(R - L >> k & 1) upd(G[k][l][r], g[0][l][r]);
upd(G[k][l][r], G[k-1][l][r]);
if(!(L >> k & 1)) upd(F[k][l][r], F[k-1][l][r]);
}
#ifdef DEBUG
rep(k, 0, maxlog - 1) printf("f[%d][%d][%d] = %d\n", k, l, r, f[k][l][r]);
#endif
return ;
}
void work() {
n = read(); L = read(); R = read();
rep(i, 1, n) S[i] = read() + S[i-1];
memset(vis, 0, sizeof(vis));
rep(k, 0, maxlog - 1) rep(i, 1, n + 1) rep(j, 1, n + 1) {
f[k][i][j] = F[k][i][j] = oo;
g[k][i][j] = G[k][i][j] = i <= j ? oo : 0;
}
dp(1, n);
printf("%d\n", f[0][1][n] < oo ? f[0][1][n] : 0);
return ;
}
int main() {
freopen("A.in", "r", stdin);
freopen("A.out", "w", stdout);
int T = read();
while(T--) work();
fclose(stdin);
fclose(stdout);
return 0;
}
B
試題描述
Scape 一到機房,所有做題的人便都看着他笑,有的叫道,“Scape,你一定又被標准分倍殺了!”他不回答,對櫃里說,“測兩個程序,看一眼成績單。”便拷出兩個程序。他們又故意的高聲嚷道,“你怎么歐拉回路和逆序對都WA了!”……
Scape 知道,以上的故事只是 OI 生涯里的一個意外,為了證明自己,他決定教你 Border 的四種求法。
給一個小寫字母字符串 \(S\),\(q\) 次詢問每次給出 \(l,r\) ,求 \(s[l..r]\) 的 Border 。
Border: 對於給定的串 \(s\) ,最大的 \(i\) 使得 \(s[1..i] = s[|s|-i+1..|s|]\),\(|s|\) 為 \(s\) 的長度。
輸入
第一行一個字符串 \(S\) 。
第二行一個整數 \(q\) 表示詢問個數。
接下來的 \(q\) 行每行兩個整數 \(l,r\) 表示一個詢問。
輸出
對於每組詢問輸出答案。
輸入示例
abbabbaa
3
1 8
1 7
2 7
輸出示例
1
4
3
數據規模及約定
對於 \(30\%\) 的數據,\(n,q \le 1000\)。
對於 \(50\%\) 的數據,\(n,q \le 20000\)。
對於另外 \(30\%\) 的數據,答案至少為 \(r-l+1\) 的一半。
對於 \(100\%\) 的數據,\(n,q \le 10^5\)。
題解
目前不會做
day4
神奇的鍾點(time)
試題描述
一天,小 L 看到了 \(3\) 塊鍾表,分別顯示着 \(01:08\)、\(03:40\)、\(13:52\)。小 L 發現,每塊表上的時間都是 \(hh:mm\) 的形式,其中 \(hh\) 表示小時,\(mm\) 表示分鍾,而且都不是整點(即 \(0 \le hh < 24, 1 \le mm < 60\))。
回想起小學數學老師剛剛講的關於比例的知識,\(hh:mm\) 也表示一個比,它的值等於 \(\frac{hh}{mm}\)。這時小 L 發現了一件神奇的事情:把這三個鍾點加起來會得到 \(18:40\),這也是一個不是整點的合法時間(注意小時數要小於 \(24\)),它對應的比值 \(\frac{9}{20}\) 與這三個鍾點對應的比值 \(\frac{1}{8}\)、\(\frac{3}{40}\)、\(\frac{1}{4}\) 之和竟然相等!
現在小 L 想知道,把所有的滿足這個神奇的性質的 \(3\) 個鍾點組成的鍾點組按照字典序排序后,第 \(k\) 小的是什么。
輸入
輸入只包含一個正整數 \(k\)。
輸出
輸出字典序第 \(k\) 小的滿足題目所述神奇性質的鍾點組,以一個空格隔開,詳見樣例輸出。如果答案不存在,輸出“\(-1\)”(不含引號)。
輸入示例1
65432
輸出示例1
01:08 03:40 13:52
輸入示例2
1
輸出示例2
00:01 00:01 00:01
輸入示例3
58
輸出示例3
00:01 00:02 00:01
輸入示例4
2000000000
輸出示例4
-1
數據規模及約定
對於 \(30\%\) 的數據,\(k \le 1000\)。
對於 \(60\%\) 的數據,\(k \le 20000\)。
對於 \(100\%\) 的數據,\(1 \le k \le 2 \times 10^9\)。
題解
分段打表、暴力剪枝均可。
(我是 \(1000\) 個打一次表,打表程序就是下面的程序改一下即可)
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cctype>
#include <algorithm>
using namespace std;
#define rep(i, s, t) for(int i = (s), mi = (t); i <= mi; i++)
#define dwn(i, s, t) for(int i = (s), mi = (t); i >= mi; i--)
int read() {
int x = 0, f = 1; char c = getchar();
while(!isdigit(c)){ if(c == '-') f = -1; c = getchar(); }
while(isdigit(c)){ x = x * 10 + c - '0'; c = getchar(); }
return x * f;
}
char Ans[130][50] = {
"00:01 00:01 00:01",
"00:01 00:22 00:13",
"00:02 00:07 00:26",
"00:02 00:35 00:04",
"00:03 00:15 00:28",
"00:04 00:02 00:25",
"00:04 00:26 00:29",
"00:05 00:10 00:29",
"00:05 00:57 17:34",
"00:06 00:21 00:31",
"00:07 00:07 00:22",
"00:07 00:47 00:02",
"00:08 00:19 00:32",
"00:09 00:06 00:13",
"00:09 00:53 01:02",
"00:10 00:19 00:17",
"00:11 00:06 00:27",
"00:11 01:08 00:57",
"00:12 00:23 00:11",
"00:13 00:07 00:20",
"00:13 08:42 08:56",
"00:14 00:33 00:06",
"00:15 00:16 00:28",
"00:16 00:03 00:31",
"00:16 04:08 06:54",
"00:17 00:26 00:07",
"00:18 00:15 00:16",
"00:19 00:02 00:02",
"00:19 05:50 06:15",
"00:20 00:45 01:05",
"00:21 00:14 00:12",
"00:22 00:05 00:27",
"00:22 12:57 05:19",
"00:23 05:45 07:18",
"00:24 02:26 00:49",
"00:25 00:17 00:02",
"00:26 00:08 00:02",
"00:27 00:03 00:05",
"00:27 13:44 03:33",
"00:28 07:49 00:39",
"00:29 07:15 03:36",
"00:30 05:30 06:36",
"00:31 02:16 00:37",
"00:32 03:12 05:40",
"00:33 01:30 07:42",
"00:34 01:52 01:26",
"00:35 02:30 05:15",
"00:36 03:24 15:32",
"00:37 03:15 12:40",
"00:38 08:32 00:26",
"00:39 11:33 04:12",
"00:40 11:22 10:20",
"00:42 00:06 00:10",
"00:43 00:19 05:10",
"00:44 02:20 04:16",
"00:45 07:18 10:09",
"00:47 00:03 00:07",
"00:48 03:08 10:20",
"00:49 16:32 01:06",
"00:51 02:08 12:16",
"00:53 00:03 00:02",
"00:54 09:18 00:08",
"00:56 08:48 00:10",
"00:59 00:05 10:40",
"01:03 08:32 03:45",
"01:06 09:54 07:33",
"01:10 10:50 05:40",
"01:15 02:45 15:36",
"01:20 03:45 08:15",
"01:25 00:38 00:47",
"01:30 14:45 01:30",
"01:44 00:22 05:22",
"02:04 00:39 11:34",
"02:08 11:26 07:52",
"02:14 00:46 04:21",
"02:20 01:30 11:55",
"02:24 13:36 06:36",
"02:32 03:36 04:24",
"02:40 03:20 09:30",
"02:55 14:40 00:09",
"03:08 01:24 00:40",
"03:15 01:36 16:45",
"03:21 00:58 00:09",
"03:30 03:30 08:35",
"03:36 18:40 01:24",
"03:50 03:15 02:20",
"04:08 00:37 00:25",
"04:15 05:24 03:45",
"04:22 00:40 07:22",
"04:30 07:35 02:30",
"04:40 01:14 00:41",
"04:48 10:24 00:18",
"05:09 02:18 00:45",
"05:18 03:45 14:15",
"05:30 00:11 01:57",
"05:40 00:28 04:32",
"05:52 02:13 09:15",
"06:12 00:41 14:28",
"06:22 00:27 08:44",
"06:32 12:24 02:32",
"06:44 00:24 07:14",
"07:05 00:45 12:20",
"07:16 02:04 07:56",
"07:28 02:32 07:32",
"07:42 00:18 13:48",
"07:55 02:20 00:29",
"08:15 05:20 03:45",
"08:30 03:33 02:30",
"08:48 00:12 04:54",
"09:10 01:05 00:55",
"09:27 06:09 00:40",
"09:45 02:15 09:36",
"10:10 00:37 04:26",
"10:25 01:30 07:35",
"10:40 05:30 07:20",
"11:11 00:25 00:36",
"11:35 02:56 00:09",
"12:12 00:12 00:49",
"12:32 01:40 04:15",
"12:52 07:26 00:22",
"13:32 03:36 04:24",
"14:14 00:45 00:16",
"14:49 04:35 02:16",
"15:36 00:35 01:01",
"16:32 00:40 04:06",
"17:44 00:37 03:12",
"19:14 01:04 02:56",
"22:44 00:25 00:37"
};
int gcd(int a, int b) { return b ? gcd(b, a % b) : a; }
void Nxt(int& a1, int& b1, int& a2, int& b2, int& a3, int& b3) {
b3++;
if(b3 < 60) return ;
b3 = 1; a3++;
if(a3 < 24) return ;
a3 = 0; b2++;
if(b2 < 60) return ;
b2 = 1; a2++;
if(a2 < 24) return ;
a2 = 0; b1++;
if(b1 < 60) return ;
b1 = 1; a1++;
return ;
}
int main() {
freopen("time.in", "r", stdin);
freopen("time.out", "w", stdout);
int n = read();
if(n > 127034) return puts("-1"), 0;
int cnt = n / 1000 * 1000; if(!cnt) cnt = 1;
char *str = Ans[n/1000];
int a1, b1, a2, b2, a3, b3;
sscanf(str, "%d:%d %d:%d %d:%d", &a1, &b1, &a2, &b2, &a3, &b3);
for(;; Nxt(a1, b1, a2, b2, a3, b3)) {
int tA = a1 + a2 + a3 + (b1 + b2 + b3) / 60, tB = (b1 + b2 + b3) % 60,
A = a1 * b2 * b3 + a2 * b1 * b3 + a3 * b1 * b2, B = b1 * b2 * b3;
if(tA >= 24) continue;
int g = gcd(tA, tB); tA /= g; tB /= g;
g = gcd(A, B); A /= g; B /= g;
if(tA == A && tB == B)
if(cnt++ == n) return printf("%02d:%02d %02d:%02d %02d:%02d\n", a1, b1, a2, b2, a3, b3), 0;
}
fclose(stdin);
fclose(stdout);
return 0;
}
最長上升子序列(lis)
試題描述
現在有一個長度為 \(n\) 的隨機排列,求它的最長上升子序列長度的期望。
為了避免精度誤差,你只需要輸出答案模 \(998244353\) 的余數。
輸入
輸入只包含一個正整數 \(n\)。
輸出
輸出只包含一個非負整數,表示答案模 \(998244353\) 的余數。
可以證明,答案一定為有理數,設其為 \(\frac{a}{b}\)(\(a\)、\(b\) 為互質的整數),你輸出的整數為 \(x\),則你需要保證 \(0 \le x < 998244353\) 且 \(a\) 與 \(bx\) 模 \(998244353\) 同余。
輸入示例1
1
輸出示例1
1
輸入示例2
2
輸出示例2
499122178
輸入示例3
3
輸出示例3
2
數據規模及約定
對於 \(100\%\) 的數據,\(1 \le n \le 28\)。
共有 \(25\) 組數據,對於第 \(i\) 組數據(\(1 \le i\ le 25\)),\(n=i+3\)。
題解
狀壓 dp + 打表。考慮我們求最長上升子序列的過程,有一個 \(g\) 數組,\(g_i\) 表示長度為 \(i\) 的最長上升子序列的最后一位的最小值。那么我們的狀態就維護這個數組。由於 \(g_i\) 一定是單調增的(由於沒有重復元素),我們可以記錄那些值出現在了 \(g\) 數組中。
於是 \(f(i, S)\) 表示填上了排列的前 \(i\) 位,\(g\) 數組中有集合 \(S\) 中的數的概率。轉移的時候枚舉最后一位填寫哪個數字,假設填寫了數字 \(k\),那么前 \(i\) 位中 \(\ge k\) 的數字都 \(+1\),不難發現這樣做是正確的(形象地想象我們在原來的 \(1 \sim i\) 的排列中插入了數字 \(k\),那么 \(\ge k\)
的部分就都要騰出一個位置)。
由於 \(f(i, S)\) 的中的 \(S\) 最多有 \(2^i\) 種狀態,所以狀態數是 \(O(2^n)\) 的,轉移數 \(O(n)\),所以總復雜度是 \(O(2^nn)\) 的。
打表程序:
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cctype>
#include <algorithm>
#include <vector>
using namespace std;
#define rep(i, s, t) for(int i = (s), mi = (t); i <= mi; i++)
#define dwn(i, s, t) for(int i = (s), mi = (t); i >= mi; i--)
int read() {
int x = 0, f = 1; char c = getchar();
while(!isdigit(c)){ if(c == '-') f = -1; c = getchar(); }
while(isdigit(c)){ x = x * 10 + c - '0'; c = getchar(); }
return x * f;
}
#define maxn 30
#define maxs 268435456
#define MOD 998244353
#define LL long long
int n, inv[maxn], bit[maxs];
vector <int> f[maxn];
int main() {
bit[0] = 0;
rep(i, 1, maxs - 1) bit[i] = bit[i^(i&-i)] + 1;
inv[1] = 1;
rep(i, 2, maxn - 1) inv[i] = (LL)(MOD - MOD / i) * inv[MOD%i] % MOD;
printf("Ans[30] = {0, ");
rep(n, 1, 28) {
rep(i, 0, n) f[i].clear(), f[i].resize(1 << i);
f[0][0] = 1;
rep(i, 0, n - 1)
rep(s, 0, f[i].size() - 1) if(f[i][s])
rep(k, 0, i) {
int ls = s & ((1 << k) - 1), rs = (s >> k); rs -= rs & -rs;
int ts = ls | (rs << k << 1) | (1 << k);
f[i+1][ts] += (LL)f[i][s] * inv[i+1] % MOD;
if(f[i+1][ts] >= MOD) f[i+1][ts] -= MOD;
}
int ans = 0, x = 0;
rep(s, 0, f[n].size() - 1) if(f[n][s]) {
x += f[n][s]; if(x >= MOD) x -= MOD;
ans += (LL)bit[s] * f[n][s] % MOD;
if(ans >= MOD) ans -= MOD;
}
printf("%d%s", ans, n < 28 ? ", " : "};\n");
}
return 0;
}
最終程序:
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cctype>
#include <algorithm>
using namespace std;
#define rep(i, s, t) for(int i = (s), mi = (t); i <= mi; i++)
#define dwn(i, s, t) for(int i = (s), mi = (t); i >= mi; i--)
int read() {
int x = 0, f = 1; char c = getchar();
while(!isdigit(c)){ if(c == '-') f = -1; c = getchar(); }
while(isdigit(c)){ x = x * 10 + c - '0'; c = getchar(); }
return x * f;
}
int Ans[30] = {0, 1, 499122178, 2, 915057326, 540715694, 946945688, 422867403, 451091574, 317868537, 200489273, 976705134, 705376344, 662845575, 331522185, 228644314, 262819964, 686801362, 495111839, 947040129, 414835038, 696340671, 749077581, 301075008, 314644758, 102117126, 819818153, 273498600, 267588741};
int main() {
freopen("lis.in", "r", stdin);
freopen("lis.out", "w", stdout);
int n = read();
printf("%d\n", Ans[n]);
fclose(stdin);
fclose(stdout);
return 0;
}
拓撲序列(topo)
試題描述
小 C 最近學習了拓撲排序的相關知識。對一個有向無環圖 \(G\) 進行拓撲排序,是將 \(G\) 中所有頂點排成一個線性序列,使得對圖 \(G\) 中任意一條有向邊 \((u, v)\),\(u\) 在線性序列中出現在 \(v\) 之前。例如,如果圖 \(G\) 的點集為 \(\{1, 2, 3, 4\}\),邊集為 \(\{(1, 2), (1, 3), (2, 4), (3, 4)\}\),那么 \((1, 2, 3, 4)\) 和 \((1, 3, 2, 4)\) 都是圖 \(G\) 的拓撲序列。
現在小 C 對一個簡單(無重邊)有向無環圖進行了拓撲排序,但不小心把原圖弄丟了。
除了拓撲序列,小 C 只記得原圖的邊數為 \(k\),而且圖中存在一個頂點 \(u\) 可以到達其他所有頂點。他想知道有多少個滿足上述要求的簡單有向無環圖。由於答案可能很大,你只需要輸出答案模 \(m\) 的余數。
輸入
輸入第一行包含 \(3\) 個整數 \(n\)、\(k\)、\(m\)。
第二行是空格隔開的 \(n\) 個正整數 \(a_1, a_2, \cdots , a_n\),表示原圖的一個拓撲序列,保證是 \(1\) 到 \(n\) 的一個排列。
輸出
僅輸出一個整數,表示滿足要求的簡單有向無環圖個數模 \(m\) 的余數。
輸入示例
4 4 4
1 2 3 4
輸出示例
1
數據規模及約定
對於 \(100\%\) 的數據,\(0 \le k \le n \le 200000\),\(1 \le m \le 10^{200000}\)。
數據點編號 | $n \le$ | $m \le$ | 約束 |
---|---|---|---|
1 - 2 | $5$ | $10^9$ | 無 |
3 | $10$ | $k = n - 1$ | |
4 - 5 | 無 | ||
6 | $200000$ | $k = n - 1$ | |
7 - 9 | 無 | ||
10 - 11 | $10^{500}$ | $k = n - 1$ | |
12 | 無 | ||
13 | $10^{18000}$ | $k = n - 1$ | |
14 | $10^{19000}$ | ||
15 | $10^{20000}$ | 無 | |
16 | $45000$ | $10^{200000}$ | $k = n - 1$ |
17 | $50000$ | ||
18 | $60000$ | 無 | |
19 | $150000$ | $10^{150000}$ | $k = n - 1$ |
20 | $180000$ | ||
21 | $200000$ | 無 | |
22 | $150000$ | $10^{200000}$ | $k = n - 1$ |
23 | $200000$ | ||
24 | $180000$ | 無 | |
25 | $200000$ |
題解
當 \(k = n - 1\) 時答案就是 \((n-1)!\);當 \(k = n\) 時設 \(f(i, 0)\) 表示前 \(i\) 個點,連接了 \(i-1\) 條邊的方案數,\(f(i, 1)\) 表示前 \(i\) 個點,連接了 \(i\) 條邊的方案數 dp 就好了。
喪心病狂高精度部分自動忽略
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cctype>
#include <algorithm>
using namespace std;
#define rep(i, s, t) for(int i = (s), mi = (t); i <= mi; i++)
#define dwn(i, s, t) for(int i = (s), mi = (t); i >= mi; i--)
int read() {
int x = 0, f = 1; char c = getchar();
while(!isdigit(c)){ if(c == '-') f = -1; c = getchar(); }
while(isdigit(c)){ x = x * 10 + c - '0'; c = getchar(); }
return x * f;
}
#define maxn 200010
#define LL long long
int n, k, MOD, f[maxn][2];
int main() {
freopen("topo.in", "r", stdin);
freopen("topo.out", "w", stdout);
n = read(); k = read(); MOD = read();
if(k < n - 1) return puts("0"), 0;
if(!k) {
if(MOD == 1) return puts("0"), 0;
return puts("1"), 0;
}
if(k == n - 1) {
int ans = 1;
rep(i, 1, n - 1) ans = (LL)ans * i % MOD;
printf("%d\n", ans);
}
else {
f[1][0] = 1;
rep(i, 2, n) {
f[i][0] = (LL)f[i-1][0] * (i - 1) % MOD;
f[i][1] = (LL)f[i-1][0] * (((LL)(i - 1) * (i - 2) >> 1) % MOD) % MOD + (LL)f[i-1][1] * (i - 1) % MOD;
if(f[i][1] >= MOD) f[i][1] -= MOD;
}
printf("%d\n", f[n][1]);
}
fclose(stdin);
fclose(stdout);
return 0;
}