D - Determinant(行列式化简)
题目大意
给出行列式 M i j = { a i b j i ≠ j a i b j + x i = j M_{ij} = \left\{\begin{array}{rcl} a_ib_j & i \neq j \\ a_ib_j+x & i = j \end{array}\right. Mij={aibjaibj+xi=ji=j,求行列式的值。
解题思路
这道题考了一个不太常见的点——行列式化简计算。比赛时队友推出来了我没推出来,课下好好总结了一下,见这里。
本题可以通过升阶法和数学归纳法(对低阶行列式初等变换找到规律)求解,由于过程复杂就不再贴出。
最后的答案是 a n s = x n + x n − 1 ∑ i = 1 n a i b i ans = x^n + x^{n-1}\sum_{i=1}^na_ib_i ans=xn+xn−1∑i=1naibi
//
// Created by Happig on 2020/10/31
//
#include <bits/stdc++.h>
#include <unordered_map>
#include <unordered_set>
using namespace std;
#define fi first
#define se second
#define pb push_back
#define ins insert
#define Vector Point
#define ENDL "\n"
#define lowbit(x) (x&(-x))
#define mkp(x, y) make_pair(x,y)
#define mem(a, x) memset(a,x,sizeof a);
typedef long long ll;
typedef long double ld;
typedef unsigned long long ull;
typedef pair<int, int> pii;
typedef pair<ll, ll> pll;
typedef pair<double, double> pdd;
const double eps = 1e-8;
const double pi = acos(-1.0);
const int inf = 0x3f3f3f3f;
const double dinf = 1e300;
const ll INF = 1e18;
const int Mod = 1e9 + 7;
const int maxn = 2e5 + 10;
ll a[maxn], b[maxn];
ll qkp(ll x, ll n, ll p) {
ll ans = 1;
x %= p;
while (n) {
if (n & 1) ans = ans * x % p;
x = x * x % p;
n >>= 1;
}
return ans;
}
int main() {
//freopen("in.txt","r",stdin);
//freopen("out.txt","w",stdout);
ios_base::sync_with_stdio(0), cin.tie(0), cout.tie(0);
int n, x;
while (cin >> n >> x) {
for (int i = 1; i <= n; i++) cin >> a[i];
for (int i = 1; i <= n; i++) cin >> b[i];
ll xx = qkp(x, n - 1, Mod), ans = 0;
for (int i = 1; i <= n; i++) {
ans = (ans + xx * a[i] % Mod * b[i] % Mod) % Mod;
}
ans = (ans + xx * x % Mod) % Mod;
cout << ans << endl;
}
return 0;
}
G - Shift and Reverse
题目大意
给出一个序列 a 1 a 2 . . . a n a_1a_2...a_n a1a2...an,现在可以任意选定一个元素 a i a_i ai,然后将序列翻转为 a i . . . a 2 a 1 a n a n − 1 . . . a i + 1 a_i...a_2a_1a_{n}a_{n-1}...a_{i+1} ai...a2a1anan−1...ai+1,问整个序列能翻转为多少种不同的序列。
解题思路
这题赛时通过的并不多,我们也没开这道题。实际上,将序列想象成一个环,实际上就是可以选择一个数断开,然后逆时针可以得到一个序列,这样虽然看起来得到了 n n n种序列,但是我们对每个断开位置的新序列的位置继续断开,发现得到是一个原序列断开位置顺时针的序列,这样一共只有 2 n 2n 2n种序列,那么化环为线,使用哈希判断即可。(蒟蒻喜欢自然溢出哈希)
//
// Created by Happig on 2020/11/8
//
#include <bits/stdc++.h>
#include <unordered_map>
#include <unordered_set>
using namespace std;
#define fi first
#define se second
#define pb push_back
#define ins insert
#define Vector Point
#define ENDL "\n"
#define lowbit(x) (x&(-x))
#define mkp(x, y) make_pair(x,y)
#define mem(a, x) memset(a,x,sizeof a);
typedef long long ll;
typedef long double ld;
typedef unsigned long long ull;
typedef pair<int, int> pii;
typedef pair<ll, ll> pll;
typedef pair<double, double> pdd;
const double eps = 1e-8;
const double pi = acos(-1.0);
const int inf = 0x3f3f3f3f;
const double dinf = 1e300;
const ll INF = 1e18;
const int Mod = 1e9 + 7;
const int maxn = 2e5 + 10;
ull base = 131;
int a[maxn], b[maxn];
ull rad[maxn];
unordered_map<ull, int> mp;
int main() {
//freopen("in.txt","r",stdin);
//freopen("out.txt","w",stdout);
ios_base::sync_with_stdio(0), cin.tie(0), cout.tie(0);
int n;
rad[0] = 1;
for (int i = 1; i <= maxn >> 1; i++) {
rad[i] = rad[i - 1] * base;
}
while (cin >> n) {
mp.clear();
ull res1 = 0, res2 = 0;
for (int i = 1; i <= n; i++) {
cin >> a[i];
a[n + i] = a[i];
res1 = a[i] + res1 * base;
}
for (int i = n, j = 1; i >= 1; i--, j++) {
b[j] = a[i], b[n + j] = b[j];
res2 = b[j] + res2 * base;
}
int ans = 0;
if (res1 == res2) ans = 1;
else ans = 2;
mp[res1] = 1, mp[res2] = 1;
for (int i = n + 1; i <= 2 * n; i++) {
res1 = (res1 - rad[n - 1] * a[i - n]) * base + a[i];
if (!mp.count(res1)) {
mp[res1] = 1, ans++;
}
}
for (int i = n + 1; i <= 2 * n; i++) {
res2 = (res2 - rad[n - 1] * b[i - n]) * base + b[i];
if (!mp.count(res2)) {
mp[res2] = 1, ans++;
}
}
cout << ans << endl;
}
return 0;
}
H - Knapsack(背包+四边形不等式优化)
题目大意
01背包裸题,但是物品个数和背包容量都达到了 2 e 5 2e^5 2e5,物品的价值达到了 1 e 9 1e^9 1e9,而物品的重量却只有 100 100 100。
解题思路
假算法
网上流传的一个解法:先按重量价值的比值贪心选直到 m m m较小,然后进行DP,这样的思路实际上是错误的。在知乎上叉姐已经说了,因为数据不够强导致了这个假算法。
我的思路
我想的是,因为重量较小,对于每种重量,我么一定优先选择价值最大的,因此我们按重量分类,问题变成了多重背包,对于每种物品选择若干个,其最优价值显然是价值降序后的前缀和。这样只需使用单调队列解决完全背包问题,时间复杂度 O ( 100 ∗ m ) O(100*m) O(100∗m),很遗憾这种解法是 T L E TLE TLE的,不知道是代码问题还是解法问题,希望大佬告知!
//
// Created by Happig on 2020/10/31
//
#include <bits/stdc++.h>
#include <unordered_map>
#include <unordered_set>
using namespace std;
#define fi first
#define se second
#define pb push_back
#define ins insert
#define Vector Point
#define ENDL "\n"
#define lowbit(x) (x&(-x))
#define mkp(x, y) make_pair(x,y)
#define mem(a, x) memset(a,x,sizeof a);
typedef long long ll;
typedef long double ld;
typedef unsigned long long ull;
typedef pair<int, int> pii;
typedef pair<ll, ll> pll;
typedef pair<double, double> pdd;
const double eps = 1e-8;
const double pi = acos(-1.0);
const int inf = 0x3f3f3f3f;
const double dinf = 1e300;
const ll INF = 1e18;
const int Mod = 1e9 + 7;
const int maxn = 2e5 + 10;
vector<int> v[105]; //价值
ll sum[105][maxn];
ll f[maxn], g[maxn], q[maxn];
int main() {
//freopen("in.txt","r",stdin);
//freopen("out.txt","w",stdout);
//ios_base::sync_with_stdio(0), cin.tie(0), cout.tie(0);
int n, m;
while (scanf("%d%d", &n, &m) != EOF) {
memset(f, 0, sizeof f);
for (int i = 1; i <= 100; i++) {
sum[i][0] = 0;
v[i].clear();
}
for (int i = 1, x, y; i <= n; i++) {
scanf("%d%d", &x, &y);
v[x].push_back(y);
}
for (int i = 1; i <= 100; i++) sort(v[i].begin(), v[i].end(), greater<int>());
for (int i = 1; i <= 100; i++) {
for (int j = 0; j < v[i].size(); j++) {
sum[i][j + 1] = sum[i][j] + v[i][j];
}
}
for (int i = 1, s; i <= 100; i++) {
if (v[i].size() == 0) continue;
//物品的体积为i,取k个的价值为sum[i][k],个数为v[i].size()
s = v[i].size();
memcpy(g, f, sizeof f);
for (int j = 0; j < i; j++) { //枚举每个V%c(0<=V<=m),每一类余数相同的才会相互转移
int l = 0, r = -1;
for (int k = j; k <= m; k += i) { //枚举j+k*c<=m的所有容量
f[k] = g[k];
if (l <= r && k - s * i > q[l]) l++; //左边最大值小于当前最小剩余容量则删去
if (l <= r) f[k] = max(f[k], g[q[l]] + sum[i][(k - q[l]) / i]); //更新答案
while (l <= r && g[q[r]] - sum[i][(q[r] - j) / i] <= g[k] - sum[i][(k - j) / i])
r--; //右边小于等于当前元素的剔除
q[++r] = k; //插入当前容量
}
}
}
printf("%lld\n", f[m]);
}
return 0;
}
正确思路
因为我对DP的各种优化还没有深入学习,这篇博客讲解了四边形不等式如何优化这道题,先留坑吧!
I - Subsequence Pair(类LCS的DP)
题目大意
给出两个字符串 s , t s,t s,t,分别从中选择一个子序列,使得前者的子序列 s 0 s_0 s0小于等于后者的子序列 t 0 t_0 t0(子序列的比较就是字符串大小比较)
解题思路
如果能想到LCS,这题已经成功一半了。设 d [ i ] [ j ] d[i][j] d[i][j]为第一个串位置为 i i i,第二个串位置为 j j j的LCS的长度,第一个串的长度为 n n n,第二个串的长度为 m m m。主要是分为以下两种情况考虑:
- 若 s [ i ] = t [ j ] s[i]=t[j] s[i]=t[j],这两个字符一定会选择。因为我们不知道后面位置的大小,但是可以肯定对于 t 0 t_0 t0来说加上末尾所有字符仍会满足 s 0 ≤ t 0 s_0 \leq t_0 s0≤t0,那么此时 a n s = m a x ( a n s , 2 ∗ d [ i − 1 ] [ j − 1 ] + 1 + m − j + 1 ) ans = max(ans,2*d[i-1][j-1]+1+m-j+1) ans=max(ans,2∗d[i−1][j−1]+1+m−j+1)
- 若 s [ i ] < t [ j ] s[i] < t[j] s[i]<t[j],这两个字符也一定会选择。现在可以知道子序列的大小了因此两个串后面的都可以直接接上去。但是前面呢,显然是取前部分的LCS,即: a n s = m a x ( a n s , 2 ∗ d [ i − 1 ] [ j − 1 ] + n − i + 1 + m − j + 1 ) ans = max(ans,2*d[i-1][j-1]+n-i+1+m-j+1) ans=max(ans,2∗d[i−1][j−1]+n−i+1+m−j+1)
//
// Created by Happig on 2020/11/6
//
#include <bits/stdc++.h>
#include <unordered_map>
#include <unordered_set>
using namespace std;
#define fi first
#define se second
#define pb push_back
#define ins insert
#define Vector Point
#define ENDL "\n"
#define lowbit(x) (x&(-x))
#define mkp(x, y) make_pair(x,y)
#define mem(a, x) memset(a,x,sizeof a);
typedef long long ll;
typedef long double ld;
typedef unsigned long long ull;
typedef pair<int, int> pii;
typedef pair<ll, ll> pll;
typedef pair<double, double> pdd;
const double eps = 1e-8;
const double pi = acos(-1.0);
const int inf = 0x3f3f3f3f;
const double dinf = 1e300;
const ll INF = 1e18;
const int Mod = 1e9 + 7;
const int maxn = 2020;
char s1[2020], s2[2020];
int d[2020][2020];
int main() {
//freopen("in.txt","r",stdin);
//freopen("out.txt","w",stdout);
//ios_base::sync_with_stdio(0),cin.tie(0),cout.tie(0);
while (scanf("%s", s1 + 1) != EOF) {
scanf("%s", s2 + 1);
int n = strlen(s1 + 1), m = strlen(s2 + 1);
memset(d, 0, sizeof d);
int ans = m;
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
if (s1[i] == s2[j]) {
d[i][j] = d[i - 1][j - 1] + 1;
ans = max(ans, 2 * d[i][j] + m - j);
} else {
d[i][j] = max(d[i - 1][j], d[i][j - 1]);
if (s1[i] < s2[j]) {
ans = max(ans, 2 * d[i - 1][j - 1] + n - i + 1 + m - j + 1);
}
}
}
}
cout << ans << ENDL;
}
return 0;
}