2022ICPC杭州(ACK)
A
exgcd
void solve()
{
int n, m;
cin >> n >> m;
int sum = 0;
for(int i = 1; i <= n; i++){
int x; cin >> x;
sum += x;
}
int a = n, b = n * (n + 1) / 2;
ll s, d, k1, t;
// 求 (as + bd + sum) % m 的最小值,并求 (s, d) 的一组特解
// 考虑将取模去除:等价于求 as + bd + mt + sum 的最小值
ll g1 = exgcd(a, b, s, d); // as + bd = k1g1
ll g2 = exgcd(g1, m, k1, t); // k1g1 + mt = k2g2
// 求 k2g2 + sum 的最小值 -> sum % g2
ll ans = sum % g2;
cout << ans << "\n";
// 求答案取得最小值情况下的 k2
ll k2 = ((ans - sum) / g2) % m;
// 此时 k1g1 + mt = g2,将k1扩大k2倍,得到正确的k1
k1 = k1 * k2 % m;
// 此时 as + bd = g1,将s,d 均扩大k1倍,得到正确的s, d
s = s * k1;
d = d * k1;
// 整个式子是在模m意义下,为了保证非负,最后还需要将s,d关于m“模加模”
s = (s % m + m) % m;
d = (d % m + m) % m;
cout << s << " " << d << "\n";
}
C
被复杂题意包装起来的01背包。
简化一下题意就是:有\(n\)个物品,背包体积为\(k\),第\(i\)个物品的体积为\(p[i]\),且有\(p[i]\)种价值,其中只有一种价值对应“全部升级”(价值为\(w[i][p[i]]\)),剩下\(p[i]-1\)种价值对应“部分升级”(价值对应\(w[i][1到p[i]-1]\))。可以选择至多一个物品“部分升级”,其他必须为“全部升级”。设选取的“全部升级”的物品总体积为\(sum\),则“部分升级”物品的价值为\(w[i][k-sum]\)。求可获最大价值。
可以将物品的\(p[i]\)个价值看做:物品的体积为\(j\)时(\(j\in [1,p[i]]\)),价值为\(w[i][j]\)(可以证明这种定义方式与题意等价)。这样,就规定选取的物品存在“部分升级”时,总价值必须恰好为\(k\)。因此在初始化\(dp\)数组时必须为\(-INF\)(对于未规定选取恰好体积时初始化为\(0\))。
特点在于\(p[i]<=10\),且至多只能有一个“部分升级”的物品。所以状态可设置为:
\(dp[i][j][0/1]\):考虑前\(i\)个物品,选取物品的总体积为\(j\),且选了\(0/1\)个“部分升级”的物品,最大总价值。
可以直接暴力枚举“部分升级”物品的所有价值并转移,转移时对于第\(i\)种物品,考虑\(3\)种情况即可:
- 不选第\(i\)个物品(不代表真的不选,而是作为题中\(sum>k\)的那部分,价值为 \(0\) 地选择该物品,相当于不选。这里容易混淆)-> 从前缀的状态转移
- 第\(i\)个物品“全部升级” -> “部分升级”的\(0/1\)状态均能转移
- 第\(i\)个物品“部分升级” -> 由于最多只能选一个,则只能从已选“部分升级”的 \(0\) 状态转移
注意当\(k\)特别大时,所有物品不能装满背包,这时所有物品必须均为“全部升级”状态。而\(dp\)只处理了选取恰好体积的情况,故需要特判\(k\)很大的情况。(如果不特判,而是采用枚举\(dp[n][0到m][0/1]\)的方式更新答案,则会有一种情况导致错误:某个物品“部分升级”比“全部升级”的价值更大,导致对这个物品进行选择时,在背包体积足够时,选了该物品“部分升级”的情况。虽然这样的情况答案更优,但却是不合法的,因为在背包体积足够的情况下,所有物品必须“全部升级”。具体见WA on 7)
可以倒序枚举体积,省去01背包\(dp\)数组中物品个数的那一维。
具体实现见代码
复杂度\(O(nk*10)\)
K
字典树
考虑逆序对的贡献是怎样产生的。
分析后可发现,逆序对产生的方式有两种:
- 两个字符串的前\(i-1\)个字符相同,第\(i\)个字符不同
- 一个字符串是另一个字符串的前缀
设\(f[x][y]\)表示在给定的字符串序列中,\((str1,str2)\)对的数量,其中\((str1,str2)\)满足:
- \(str1\) 在 \(str2\) 之前
- 存在一个位置 \(i\) ,使得\(str1[1到i-1]==str2[1到i-1]\)
- \(str1[i]==x\),\(str2[i]==y\)。\(x,y\)表示字符。
则 $ans1 = $
其中\(seq[x]\)为字符\(x\)在当前给定的字母表中的位置。
计算\(ans2\):在插入某个字符串\(str\)前,用这个字符串跑一遍字典树,若可到\(str\)的末尾,则看其末尾之后的前缀数量,这些字符串都可以使得\(str\)是它们的真前缀,自然和\(str\)构成逆序对。对于每个字符串,在插入前做这样的统计即可。注意走不到末尾时不能作统计,具体实现见代码。

浙公网安备 33010602011771号