文章目录
2022牛客多校(一)
一、比赛小结
比赛链接:"蔚来杯"2022牛客暑期多校训练营1
二、题目分析及解法(基础题)
A、Villages: Landlines
签到题
题意:
给出一个发电站,给出n-1个村庄,要建立一些电塔使得发电站与村庄连通,发电站与村庄均有接收范围 [ x i − r i , x i + r i ] [x_i-r_i, x_i+r_i] [xi−ri,xi+ri] ,电塔与电塔连接成本为距离差,求使图形连通的最小成本
题解:
进行区间merge即可,将前后不相交的区间距离贡献至答案
代码:
#include <bits/stdc++.h>
#define int long long
#define pii pair<int, int>
using namespace std;
const int maxn = 2e5 + 5;
int n;
vector<pii> vec, res;
signed main() {
// freopen("in.txt", "r", stdin);
// freopen("out.txt", "w", stdout);
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
cin >> n;
int u, v;
cin >> u >> v;
vec.push_back({u - v, u + v});
for (int i = 1; i < n; i++) {
cin >> u >> v;
vec.push_back({u - v, u + v});
}
sort(vec.begin(), vec.end());
pii last = vec[0];
res.push_back(last);
for (auto e : vec) {
if (e.first <= last.second) {
last = {min(last.first, e.second), max(last.second, e.second)};
res.pop_back();
} else
last = e;
res.push_back(last);
}
int ans = 0;
for (int i = 1; i < res.size(); i++)
ans += (res[i].first - res[i - 1].second);
cout << ans << endl;
return 0;
}
C、Grab the Seats!
中档题
题意:
在影院里有座位,有屏幕,定义”遮挡“为屏幕两端点与该座位形成的射线的三角区域。现在给出 k k k 个点,要求线性时间内求遮挡范围
题解:
实际上我们可以先只考虑一个屏幕的一个端点,另一个端点同理。我们来分析一下关键变量,首先是每排的第一个人所在的位子,其次是之前排所挡住当前排的第一个位子。第一个变量可以线性扫出来了,第二个变量我们稍加分析,发现可以维护其”斜率“,这个值是单调递增且易于维护的,进而整个题就做完了
另外,这个题有一个 O ( m log k ) O(m\log k) O(mlogk) 算法,就是利用李超树直接求解
代码:
#include <bits/stdc++.h>
#define int long long
#define pii pair<int, int>
using namespace std;
const int inf = 0x3f3f3f3f3f3f3f3f;
const int maxn = 2e5 + 5;
int n, m, k, q;
pii per[maxn];
int front[maxn], ft[maxn];
struct frac {
int a, b;
bool operator<(const frac &f) const { return a * f.b < f.a * b; }
};
void solve() {
fill(ft + 1, ft + m + 1, n);
fill(front + 1, front + m + 1, n + 1);
for (int i = 1; i <= k; i++)
front[per[i].second] = min(front[per[i].second], per[i].first);
frac L = {inf, 1ll};
for (int i = 2; i <= m; i++) {
if (L.a != inf) ft[i] = min(ft[i], (L.a * (i - 1) - 1) / L.b);
L = min(L, (frac){front[i], i - 1ll});
}
frac R = {inf, 1ll};
for (int i = m - 1; i >= 1; i--) {
if (R.a != inf) ft[i] = min(ft[i], (R.a * (m - i) - 1) / R.b);
R = min(R, (frac){front[i], m - i});
}
int ans = 0;
for (int i = 1; i <= m; i++) {
ft[i] = min(ft[i], front[i] - 1ll);
ans += ft[i];
}
cout << ans << endl;
}
signed main() {
// freopen("in.txt", "r", stdin);
// freopen("out.txt", "w", stdout);
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
cin >> n >> m >> k >> q;
for (int i = 1; i <= k; i++) {
int x, y;
cin >> x >> y;
per[i] = {x, y};
}
for (int i = 1; i <= q; i++) {
int p, x, y;
cin >> p >> x >> y;
per[p] = {x, y};
solve();
}
return 0;
}
D、Mocha and Railgun
签到题
题意:
给出圆,以及圆内一点,以及攻击方式,求该点攻击范围(与圆相交部分)最大值
题解:
如图所示:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Dsp253xb-1662047660537)(file:///C:/Users/lenovo/AppData/Roaming/marktext/images/2022-08-23-18-01-29-image.png?msec=1661248906630)]
代码:
#include <bits/stdc++.h>
using namespace std;
int main() {
// freopen("in.txt", "r", stdin);
// freopen("out.txt", "w", stdout);
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
int _;
cin >> _;
while (_--) {
double d, r;
double x, y;
cin >> r;
cin >> x >> y >> d;
double dis = sqrt(x * x + y * y);
double ang1 = acos((dis - d) / r), ang2 = acos((dis + d) / r);
double ans = (ang1 - ang2) * r;
cout << fixed << setprecision(10) << ans << endl;
}
return 0;
}
G、lexicographically maximum
签到题
题意:
给出n,求 1~n之间字典序最大的那个数
题解:
考虑长度为len-1的 “999…9” ,将之与n进行比较讨论即可
代码:
#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e6 + 5;
char s[maxn];
int len;
int main() {
// freopen("in.txt", "r", stdin);
// freopen("out.txt", "w", stdout);
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
cin >> s;
len = strlen(s);
int t = -1;
for (int i = 0; i < len; i++) {
if (s[i] != '9') break;
t = i;
}
if (t >= len - 2)
cout << s;
else
for (int i = 0; i < len - 1; i++) cout << 9;
return 0;
}
H、Fly
dp+NTT,中档题
题意:
n n n 种物品每种有无数个,第 i 个物品的体积为 a i a_i ai ,求解选择物品总体积不超过 M M M 的方案数。此外,有 k k k 个限制,第 i i i 个限制形如第 b i b_i bi 个物品所选的数量二进制表示下从低到高第 c i c_i ci 位必须为 0。即
{ ∑ i = 1 n a i x i ≤ M x b i & 2 c i = 0 , i = 1 , 2 , … , k \begin{cases}\displaystyle\sum_{i=1}^na_ix_i\le M \\ x_{b_i}\&2^{c_i}=0, i=1, 2, \dots, k \end{cases} ⎩ ⎨ ⎧i=1∑naixi≤Mxbi&2ci=0,i=1,2,…,k
题解:
从第一个约束式其实可以看出,这题可能是个完全背包问题。再分析一下第二个条件的话,发现 x i x_i xi 最多128位。因此,我们可以通过适当变形,把 a i x i a_ix_i aixi 拆成 128 个 0-1 背包,最后来处理这个总的 0-1 背包即可。最后,我们发现这个整体的 0-1 背包规模似乎有点大,通常的 0-1 背包是 O ( n 2 ) O(n^2) O(n2) 的,因此我们需要进行一些优化,这里,我们采用了 NTT 来进行优化。
具体地,利用类似二进制分组的方法将完全背包转化为连续做 m = log M m=\log M m=logM 个 01 背包,其中第 i i i 个 01 背包由 n n n 个物品组成,其中第 j 个物品的体积为 2 i × a j 2^i\times a_j 2i×aj , i = 0 , 1 , … , m − 1 i=0, 1, \dots ,m-1 i=0,1,…,m−1 。并将 k k k 个限制所对应的物品从背包中剔去。化成数学表达式为:
f = ∏ i = 1 m ∏ j ( 1 + x 2 i × a j ) = ∑ i t i x i , a n s = ∑ i ≤ M t i \displaystyle f= \prod_{i=1}^m\prod_j(1+x^{2^i\times a_j})=\sum_{i}t_ix^i,\ ans=\sum_{i\le M}t_i f=i=1∏mj∏(1+x2i×aj)=i∑tixi, ans=i≤M∑ti
设 f ( i , j , b ) f(i, j, b) f(i,j,b) 为第 i i i 个 01 背包进行完毕,选择物品总体积为 V V V 的方案数,实际上,我们在选择第 i i i 个背包的时候,关键变量有三个,一个是当前所选的物品体积,一个是之前物品所选体积,还有一个是目前物品是否有超过 M M M 的风险。对于前两个,我们发现,我们只需要维护 [ V 2 i ] \left[\displaystyle \frac{V}{2^i}\right] [2iV] 即可,而对于第三项,可以类比数位dp里的limit参数,我们新设一个变量 b b b 来表示 [ V ( m o d 2 ) i > M ( m o d 2 ) i ] \left[ V \pmod 2^i>M \pmod 2^i \right] [V(mod2)i>M(mod2)i] 即可,进而最终答案为 f ( m , 0 , 0 ) f(m,0,0) f(m,0,0) ,而 j < ∑ l = 0 i n 2 l < 2 n \displaystyle j<\sum_{l=0}^i\frac{n}{2^l}<2n j<l=0∑i2ln<2n
设 g ( i , j ) g(i, j) g(i,j) 为第 i i i 个背包选择物品总体积为 j × 2 i j\times 2^i j×2i 的方案数,那么有:
g ( i , j ) = [ x j ] ∏ ( l , i ) ∉ L i m i t ( 1 + x a l ) = [ x j ] ∏ l = 1 n ( 1 + x a l ) ∏ ( l , i ) ∈ L i m i t ( 1 + x a l ) \displaystyle g(i,j)=[x^j]\prod_{(l, i)\not \in Limit}(1+x^{a_l})=[x^j]\frac{\prod_{l=1}^n(1+x^{a_l})}{\prod_{(l, i) \in Limit}(1+x^{a_l})} g(i,j)=[xj](l,i)∈Limit∏(1+xal)=[xj]∏(l,i)∈Limit(1+xal)∏l=1n(1+xal)
于是有递推式:
f ( i , j , b ) × g ( i + 1 , l ) → f ( i + 1 , [ j + l 2 ] , B ( ( j + l ) ( m o d 2 ) , M i , b ) \displaystyle f(i,j,b)\times g(i+1,l) \rarr f(i+1,\left[\frac{j+l}{2} \right],B((j+l)\pmod 2, M_i,b) f(i,j,b)×g(i+1,l)→f(i+1,[2j+l],B((j+l)(mod2),Mi,b)
B ( x , y , z ) = [ x > y ∨ ( ( x = y ) ∧ z ] B(x,y,z)=[x>y\lor((x=y)\land z ] B(x,y,z)=[x>y∨((x=y)∧z]
代码:
#include <bits/stdc++.h>
using namespace std;
namespace poly {
typedef long long ll;
const int mod = 998244353;
const int maxn = 1e6 + 10;
ll invi[maxn];
// 记得先init()!!!!!!!!!
void init() {
invi[1] = 1;
for (int i = 2; i < maxn; i++) {
invi[i] = (mod - mod / i) * invi[mod % i] % mod;
}
}
// 快速幂
ll quickpow(ll x, ll y) {
ll ans = 1;
while (y) {
if (y & 1) ans = ans * x % mod;
y >>= 1;
x = x * x % mod;
}
return ans;
}
// 二次剩余
ll modsqrt(ll x) {
if (mod == 2) return x % mod;
if (quickpow(x, mod - 1 >> 1) != 1) return -1;
ll ans = 0;
if (mod % 4 == 3)
ans = quickpow(x, mod + 1 >> 2);
else {
ll b;
for (b = rand() % mod; quickpow(b, mod - 1 >> 1) == 1; b = rand() % mod)
;
ll i = mod - 1 >> 1, k = 0;
do {
i >>= 1;
k >>= 1;
if ((quickpow(x, i) * quickpow(b, k) + 1) % mod == 0) k += (mod - 1 >> 1);
} while (i % 2 == 0);
ans = quickpow(x, i + 1 >> 1) * quickpow(b, k >> 1) % mod;
}
if (ans * 2 > mod) ans = mod - ans;
return ans;
}
// 蝴蝶变换
void change(ll* a, int len) {
static int rev[maxn];
rev[0] = 0;
for (int i = 0; i < len; i++) {
rev[i] = (rev[i >> 1] >> 1);
if (i & 1) rev[i] |= len >> 1;
}
for (int i = 0; i < len; i++)
if (i < rev[i]) swap(a[i], a[rev[i]]);
}
// flag=1 -> NTT , flag=-1 -> INTT , O(nlogn)
void NTT(ll* a, int len, int flag) {
change(a, len);
for (int h = 2; h <= len; h <<= 1) {
ll uni = quickpow(3, (mod - 1) / h); // 3 为 mod = 998244353 的原根
if (flag == -1) uni = quickpow(uni, mod - 2);
for (int i = 0; i < len; i += h) {
ll w = 1;
for (int j = i; j < i + h / 2; j++) {
ll u = a[j] % mod;
ll v = w * a[j + h / 2] % mod;
a[j] = (u + v) % mod;
a[j + h / 2] = (u - v + mod) % mod;
w = w * uni % mod;
}
}
}
if (flag == -1) {
ll inv = quickpow(len, mod - 2);
for (int i = 0; i < len; i++) a[i] = a[i] * inv % mod;
}
}
// 多项式乘法,n m 分别为 a b 最高次数
void polymul(ll* a, ll* b, ll* ans, int n, int m) {
static ll tpa[maxn], tpb[maxn];
memcpy(tpa, a, sizeof(ll) * (n + 1));
memcpy(tpb, b, sizeof(ll) * (m + 1));
int len = 1;
for (; len < n + m + 1; len <<= 1)
;
memset(tpa + n + 1, 0, sizeof(ll) * (len - n));
memset(tpb + m + 1, 0, sizeof(ll) * (len - m));
NTT(tpa, len, 1);
NTT(tpb, len, 1);
for (int i = 0; i <= len; i++) ans[i] = tpa[i] * tpb[i] % mod;
NTT(ans, len, -1);
}
// // 多项式求逆,ans为最终答案数组, O(nlogn)
// void polyinv(ll* a, ll* ans, int len) {
// static ll tp[maxn];
// memset(tp, 0, sizeof(ll) * (len << 1));
// memset(ans, 0, sizeof(ll) * (len << 1));
// ans[0] = quickpow(a[0], mod - 2);
// for (int h = 2; h <= len; h <<= 1) {
// memcpy(tp, a, sizeof(ll) * h);
// NTT(tp, h << 1, 1);
// NTT(ans, h << 1, 1);
// for (int i = 0; i < (h << 1); i++)
// ans[i] = ans[i] * ((2 + mod - tp[i] * ans[i] % mod) % mod) % mod;
// NTT(ans, h << 1, -1);
// memset(ans + h, 0, sizeof(ll) * h);
// }
// }
// // 多项式除法,a / b = ans1, a % b = ans2
// void polydiv(ll* a, ll* b, ll* ans1, ll* ans2, int n, int m) {
// static ll tp0[maxn];
// int len = 1;
// for (; len < n - m + 1; len <<= 1)
// ;
// memset(tp0, 0, sizeof(ll) * len);
// for (int i = 0; i <= n; i++) ans1[n - i] = a[i];
// for (int i = 0; i <= m; i++) ans2[m - i] = b[i];
// for (int i = n - m + 1; i <= m; i++) ans2[i] = 0;
// polyinv(ans2, tp0, len);
// polymul(ans1, tp0, tp0, n, n - m);
// for (int i = 0; i <= n - m; i++) ans1[i] = tp0[n - m - i];
// polymul(b, ans1, ans2, m, n - m);
// for (int i = 0; i < m; i++) ans2[i] = (a[i] - ans2[i] + mod) % mod;
// }
// // 多项式求导
// void qiudao(ll* a, ll* ans, int len) {
// for (int i = 1; i < len; i++) ans[i - 1] = a[i] * i % mod;
// ans[len - 1] = 0;
// }
// // 多项式积分
// void jifen(ll* a, ll* ans, int len) {
// for (int i = len - 1; i; i--) ans[i] = a[i - 1] * invi[i] % mod;
// ans[0] = 0; // 积分常数C
// }
// // 多项式ln,a[0]!=0,O(nlogn)
// void polyln(ll* a, ll* ans, int len) {
// static ll tp1[maxn];
// qiudao(a, tp1, len);
// memset(tp1 + len, 0, sizeof(ll) * len);
// polyinv(a, ans, len);
// NTT(ans, len << 1, 1);
// NTT(tp1, len << 1, 1);
// for (int i = 0; i < (len << 1); i++) tp1[i] = tp1[i] * ans[i] % mod;
// NTT(tp1, len << 1, -1);
// jifen(tp1, ans, len);
// }
// // 多项式exp,a[0]==0,O(nlogn)
// void polyexp(ll* a, ll* ans, int len) {
// static ll tp2[maxn];
// memset(tp2, 0, sizeof(ll) * (len << 1));
// memset(ans, 0, sizeof(ll) * (len << 1));
// ans[0] = 1;
// for (int h = 2; h <= len; h <<= 1) {
// polyln(ans, tp2, h);
// tp2[0] = ((a[0] + 1) % mod - tp2[0] + mod) % mod;
// for (int i = 1; i < h; i++) tp2[i] = (a[i] - tp2[i] + mod) % mod;
// memset(tp2 + h, 0, sizeof(ll) * h);
// NTT(ans, h << 1, 1);
// NTT(tp2, h << 1, 1);
// for (int i = 0; i < (h << 1); i++) ans[i] = ans[i] * tp2[i] % mod;
// NTT(ans, h << 1, -1);
// memset(ans + h, 0, sizeof(ll) * h);
// }
// }
// // 多项式开根,O(nlogn)
// void polysqrt(ll* a, ll* ans, int len) {
// static ll tp3[maxn], tp4[maxn];
// memset(tp3, 0, sizeof(ll) * (len << 1));
// memset(tp4, 0, sizeof(ll) * (len << 1));
// memset(ans, 0, sizeof(ll) * (len << 1));
// ans[0] = modsqrt(a[0]); // 二次剩余
// ll inv2 = quickpow(2, mod - 2);
// for (int h = 2; h <= len; h <<= 1) {
// polyinv(ans, tp3, h);
// memcpy(tp4, a, sizeof(ll) * h);
// NTT(tp3, h << 1, 1);
// NTT(tp4, h << 1, 1);
// NTT(ans, h << 1, 1);
// for (int j = 0; j < (h << 1); j++)
// ans[j] =
// (ans[j] * ans[j] % mod + tp4[j]) % mod * inv2 % mod * tp3[j] % mod;
// NTT(ans, h << 1, -1);
// memset(ans + h, 0, sizeof(ll) * h);
// }
// }
} // namespace poly
typedef long long ll;
const int maxn = 1e6 + 10;
const int mod = 998244353;
int a[maxn];
vector<int> v[60];
ll dp[maxn];
ll f[2][maxn], tp[2][maxn];
void solve() {
int n, k;
ll m;
cin >> n >> m >> k;
int sum = 0;
for (int i = 1; i <= n; i++) {
cin >> a[i];
sum += a[i];
}
for (int i = 1; i <= k; i++) {
int b, c;
cin >> b >> c;
v[c].push_back(b);
}
dp[0] = 1;
for (int i = 1; i <= n; i++)
for (int j = sum; j >= a[i]; j--) dp[j] = (dp[j] + dp[j - a[i]]) % mod;
f[0][0] = 1;
for (int i = 0; i < 60; i++) {
sort(v[i].begin(), v[i].end());
v[i].erase(unique(v[i].begin(), v[i].end()), v[i].end());
for (int j : v[i])
for (int k = a[j]; k <= sum; k++)
dp[k] = (dp[k] - dp[k - a[j]] + mod) % mod;
poly::polymul(f[0], dp, f[0], sum, sum);
poly::polymul(f[1], dp, f[1], sum, sum);
for (int j = 0; j <= 2 * sum; j++) {
if ((j & 1) > ((m >> i) & 1)) {
tp[1][j >> 1] = (tp[1][j >> 1] + f[0][j] + f[1][j]) % mod;
} else if ((j & 1) == ((m >> i) & 1)) {
tp[1][j >> 1] = (tp[1][j >> 1] + f[1][j]) % mod;
tp[0][j >> 1] = (tp[0][j >> 1] + f[0][j]) % mod;
} else {
tp[0][j >> 1] = (tp[0][j >> 1] + f[0][j] + f[1][j]) % mod;
}
}
for (int i = 0; i <= 2 * sum + 1; i++) {
f[0][i] = tp[0][i];
f[1][i] = tp[1][i];
tp[0][i] = tp[1][i] = 0;
}
for (int j : v[i])
for (int k = sum; k >= a[j]; k--) dp[k] = (dp[k] + dp[k - a[j]]) % mod;
}
cout << f[0][0];
}
int main() {
// freopen("in.txt", "r", stdin);
// freopen("out.txt", "w", stdout);
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
poly::init();
solve();
return 0;
}
还有一种做法,是标答给出的,大体思路是一样的,但使用了 分治NTT
#include <bits/stdc++.h>
#define maxn 270086
using namespace std;
typedef long long ll;
const int p = 998244353;
inline int fpow(int x, int y) {
int ans = 1;
while (y) {
if (y & 1) ans = 1ll * ans * x % p;
x = 1ll * x * x % p, y >>= 1;
}
return ans;
}
const int G = 3, invG = fpow(G, p - 2);
int pos[maxn], prt[2][30];
inline void init() {
for (int i = 1, j = 2; i <= 23; i++, j <<= 1)
prt[0][i] = fpow(G, (p - 1) / j);
for (int i = 1, j = 2; i <= 23; i++, j <<= 1)
prt[1][i] = fpow(invG, (p - 1) / j);
}
inline void NTT(int *a, int lim, int type) {
for (int i = 0; i < lim; i++)
if (i < pos[i]) swap(a[i], a[pos[i]]);
for (int mid = 1, pw = 1; mid < lim; mid <<= 1, pw++) {
int wn = prt[type == -1][pw];
for (int i = mid << 1, j = 0; j < lim; j += i) {
int w = 1;
for (int k = 0; k < mid; k++, w = 1ll * w * wn % p) {
int x = a[j + k], y = 1ll * w * a[j + k + mid] % p;
a[j + k] = x + y;
if (a[j + k] >= p) a[j + k] -= p;
a[j + k + mid] = x - y + p;
if (a[j + k + mid] >= p) a[j + k + mid] -= p;
}
}
}
if (type == -1) {
int invlim = fpow(lim, p - 2);
for (int i = 0; i < lim; i++) a[i] = 1ll * a[i] * invlim % p;
}
}
#define ls(x) (x << 1)
#define rs(x) (x << 1 | 1)
int a[maxn], L[maxn << 2], R[maxn << 2], st[maxn], cnt, len;
int A[maxn], B[maxn];
void build(int x, int l, int r) {
if (l == r) {
L[x] = st[l], R[x] = st[l + 1] - 1;
return;
}
int mid = l + r >> 1;
build(ls(x), l, mid), build(rs(x), mid + 1, r);
int deg = R[ls(x)] - L[ls(x)] + R[rs(x)] - L[rs(x)], lim = 1;
while (lim <= deg) lim <<= 1;
for (int i = 0; i < lim; i++)
A[i] = B[i] = 0, pos[i] = (pos[i >> 1] >> 1) | ((i & 1) ? (lim >> 1) : 0);
for (int i = L[ls(x)]; i <= R[ls(x)]; i++) A[i - L[ls(x)]] = a[i];
for (int i = L[rs(x)]; i <= R[rs(x)]; i++) B[i - L[rs(x)]] = a[i];
NTT(A, lim, 1), NTT(B, lim, 1);
for (int i = 0; i < lim; i++) A[i] = 1ll * A[i] * B[i] % p;
NTT(A, lim, -1);
L[x] = L[ls(x)], R[x] = L[x] + deg;
for (int i = L[x]; i <= R[x]; i++) a[i] = A[i - L[x]];
}
inline void add(int &x, int y) {
x += y;
if (x >= p) x -= p;
}
int n, k, x, y;
ll m;
int siz[maxn];
vector<int> v[maxn];
int f[2][maxn], F[2][maxn], g[maxn];
int main() {
init();
scanf("%d%lld%d", &n, &m, &k);
int sum = 0;
for (int i = 1; i <= n; i++) {
scanf("%d", &siz[i]), sum += siz[i];
st[++cnt] = len;
for (int j = 0; j <= siz[i]; j++, len++) a[len] = j == 0 || j == siz[i];
}
st[cnt + 1] = len;
build(1, 1, n);
while (k--) {
scanf("%d%d", &x, &y);
v[y].push_back(x);
}
int lim = 1;
while (lim <= 3 * sum) lim <<= 1;
for (int i = 0; i < lim; i++)
pos[i] = (pos[i >> 1] >> 1) | ((i & 1) ? (lim >> 1) : 0);
f[0][0] = 1;
for (int i = 0; m >> i; i++) {
for (int j = 0; j <= R[1]; j++) g[j] = a[j];
for (int j = R[1] + 1; j < lim; j++) g[j] = 0;
sort(v[i].begin(), v[i].end());
v[i].erase(unique(v[i].begin(), v[i].end()), v[i].end());
for (int j = 0; j < v[i].size(); j++) {
int x = siz[v[i][j]];
for (int k = x; k <= R[1]; k++) add(g[k], p - g[k - x]);
}
NTT(f[0], lim, 1), NTT(f[1], lim, 1), NTT(g, lim, 1);
for (int j = 0; j < lim; j++)
f[0][j] = 1ll * f[0][j] * g[j] % p, f[1][j] = 1ll * f[1][j] * g[j] % p;
NTT(f[0], lim, -1), NTT(f[1], lim, -1);
for (int j = 0; j < lim; j++) {
if ((j & 1) < ((m >> i) & 1)) {
add(F[0][j >> 1], f[0][j]);
add(F[0][j >> 1], f[1][j]);
} else if ((j & 1) == ((m >> i) & 1)) {
add(F[0][j >> 1], f[0][j]);
add(F[1][j >> 1], f[1][j]);
} else {
add(F[1][j >> 1], f[0][j]);
add(F[1][j >> 1], f[1][j]);
}
}
for (int j = 0; j < lim; j++)
f[0][j] = F[0][j], f[1][j] = F[1][j], F[0][j] = F[1][j] = 0;
}
printf("%d", f[0][0]);
}
I、Chiitoitsu
中档dp题
题意:
题意挺复杂的,简单说就是,麻将里有34*4张牌,这34种牌可以进一步分成4大类(Manzu, Pinzu, Souzu, Jihai)。并且,这34类牌都对应一个字符串标号。
Serval 想在这些牌中组成 Chiitoitsu ,即由7种不同类型的手牌以及每种类型的两个组成,具体游戏规则为:
初始时,Serval 有其中的13张手牌,有且仅有2个相同类型的手牌,每个回合为:先从余堆中随机拿走一张牌,如果此时达到了 Chiitoitsu ,那么游戏结束,负责就再从手牌中丢弃一张牌到棋盘上,即不到余堆中。求成功的期望数。
题解:
其实他的策略很简单,就是在回合中,保留配对的牌,丢弃不配对的牌,而且答案显然只有7种,考试时用了暴力打表,把这7种给打出来了。用的是基础的容斥原理,没有意识到这其实是一道dp题。
回过头来看的话,其实转移方程还是挺明显的,设 d p [ i ] [ j ] dp[i][j] dp[i][j] 表示余堆还剩 i i i 张牌,手上还有 j j j 张单牌,则有:
d p [ i ] [ j ] = { 1 + i − 3 i d p [ i ] [ j ] ( j = 1 ) 1 + 3 j i d p [ i − 1 ] [ j − 2 ] + i − 3 j i d p [ i − 1 ] [ j ] ( j ≥ 2 ) dp[i][j]=\begin{cases}1+\frac{i-3}{i}dp[i][j] &(j=1) \\ 1+ \frac{3j}{i}dp[i-1][j-2]+\frac{i-3j}{i} dp[i-1][j] &(j\ge2)\end{cases} dp[i][j]={1+ii−3dp[i][j]1+i3jdp[i−1][j−2]+ii−3jdp[i−1][j](j=1)(j≥2)
代码:
// dp 做法
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int mod = 1e9 + 7;
int dp[150][15];
int inv[150];
string s;
unordered_map<string, int> mp;
int quickpow(int a, int x) {
int res = 1ll;
while (x) {
if (x & 1ll) res = res * a % mod;
a = a * a % mod;
x >>= 1;
}
return res;
}
int getinv(int x) { return quickpow(x, mod - 2); }
void init() {
for (int i = 1; i < 150; i++) inv[i] = getinv(i);
for (int i = 1; i < 150; i++) {
dp[i][1] = (1ll + (i - 3) * inv[i] % mod * dp[i - 1][1]) % mod;
for (int j = 2; j < 15; j++) {
dp[i][j] = (1ll + 3ll * j * inv[i] % mod * dp[i - 1][j - 2] +
(i - 3ll * j) * inv[i] % mod * dp[i - 1][j]);
dp[i][j] = (dp[i][j] + mod) % mod;
}
}
}
signed main() {
// freopen("in.txt", "r", stdin);
// freopen("out.txt", "w", stdout);
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
int _, cas = 1;
cin >> _;
init();
while (_--) {
mp.clear();
cin >> s;
int cnt = 0;
int len = s.size();
for (int i = 0; i < len; i += 2) {
string tmp = s.substr(i, 2);
if (!mp[tmp])
mp[tmp]++;
else
cnt++;
}
cout << "Case #" << cas++ << ": " << dp[136 - 13][13 - 2 * cnt] << endl;
}
return 0;
}
// 打表做法
#include <bits/stdc++.h>
using namespace std;
string s;
int ans[] = {927105416, 745749140, 707741534, 882102328,
781250051, 100000041, 31};
unordered_map<string, int> mp;
int main() {
// freopen("in.txt", "r", stdin);
// freopen("out.txt", "w", stdout);
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
int _, cas = 1;
cin >> _;
while (_--) {
mp.clear();
cin >> s;
int len = s.size();
int res = 0;
for (int i = 0; i < len; i += 2) {
string tmp = s.substr(i, 2);
if (!mp[tmp])
mp[tmp]++;
else
res++;
}
cout << "Case #" << cas++ << ": " << ans[res] << endl;
}
return 0;
}
J、Serval and Essay
金牌题,启发式合并裸题
题意:
给定一张 n n n 个点 m m m 条边的无重边无自环的有向图,初始时可以选择一个点染黑,若某个点的起点均为黑色,则该点可以被染黑,最大化图中黑点数量
题解:
我们首先从题目中挖掘出一些简单性质,记 S u S_u Su 为起点为 u u u 所能染黑的点的集合,不难发现有如下性质:
-
若 v ∈ S u v\in S_u v∈Su ,则 S v ∈ S u S_v \in S_u Sv∈Su
-
若 v ∈ S u v\in S_u v∈Su ,则必存在一条从 u u u 指向 v v v 的单向路径
-
若 S v ∪ S u ≠ ∅ S_v \cup S_u \not= \varnothing Sv∪Su=∅ ,则 S v ⊂ S u o r S u ⊂ S v S_v \subset S_u \ or \ S_u \sub S_v Sv⊂Su or Su⊂Sv
其实可以直接类比 SAM 的 endpos 来思考,于是我们可以看到 S u S_u Su 和 S v S_v Sv 的单向关系,更深刻地,其实是 S u S_u Su 及其连边组成了一棵有向树
抽象地,我们将 S u S_u Su 看作为树的节点, ∣ S u ∣ |S_u| ∣Su∣ 为我们的待求量, S u S_u Su 唯一地由其子树所确定,因此,我们可以采用树上启发式搜索来完成这道题目
与常规树上启发式搜索不同的是,这里的建树并未给出,并且 S u S_u Su 本身即为我们的待求量,因此需要我们来自己建树
代码:
#include <bits/stdc++.h>
#define pii pair<int, int>
using namespace std;
const int maxn = 2e5 + 10;
int n;
int pre[maxn], sz[maxn];
set<int> to[maxn], from[maxn];
void init() { // maxn 会卡
for (int i = 1; i <= n; i++) {
to[i].clear();
from[i].clear();
pre[i] = i;
sz[i] = 1;
}
}
int find(int x) {
if (pre[x] == x) return x;
return pre[x] = find(pre[x]);
}
void merge(int x, int y) {
x = find(x), y = find(y);
if (x == y) return;
if (from[x].size() > from[y].size()) swap(x, y);
pre[x] = y;
sz[y] += sz[x];
vector<pii> v;
for (auto i : from[x]) {
from[y].insert(i);
to[i].insert(y);
to[i].erase(x);
if (to[i].size() == 1) v.push_back({y, i});
}
for (auto e : v) merge(e.first, e.second);
}
int main() {
// freopen("in.txt", "r", stdin);
// freopen("out.txt", "w", stdout);
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
int _, cas = 1;
cin >> _;
while (_--) {
cin >> n;
init();
for (int i = 1; i <= n; i++) {
int k;
cin >> k;
for (int j = 1; j <= k; j++) {
int x;
cin >> x;
to[i].insert(x);
from[x].insert(i);
}
}
for (int i = 1; i <= n; i++)
if (to[i].size() == 1) merge(*to[i].begin(), i);
int ans = 0;
for (int i = 1; i <= n; i++) ans = max(ans, sz[i]);
cout << "Case #" << cas++ << ": " << ans << endl;
}
return 0;
}
三、题目分析及解法(进阶题)
补完其他场再来更,咕咕咕