传送门
- 将最上面两排作为中转站;
- 自底向下往上,之后又从上往下;
- 第三排开始每步长度为\(2\)或者\(\sqrt{2}\),上面两排步长可能为\(\sqrt{5}\)。
题意:
询问\(k\)循环的子串个数,\(1\leq k\leq 20\)。
思路:
考虑直接枚举长度为\(k\)的倍数的所有子串,显然时间复杂度过高。
注意到所有子串都满足存在一个最小循环节,\(k=1\)的情况除外,这时特判即可。
考虑对每个串直接求最小循环节,显然也会超时。
转换思路:
- 求出所有以\(x\)为循环节的串,那么考虑枚举\(x\),然后设立\(i,i+x\cdots\)这些关键点。
- 依次考虑相邻的两个关键点,通过后缀数组求出\(lcp,lcs\),得到覆盖当前关键点的串。
- 那么串对答案的贡献为\(len-kx+1\)。
注意串可能会有重复的,所以我们加个判断限制一下。
详见代码:
Code
#include
using namespace std;
typedef long long ll;
const int N = 3e5 + 5;
struct SA{ //sa:1...n Rank:0...n-1
int x[N], y[N], sa[N], c[N], height[N], Rank[N];
int f[N][20], lg[N];
int n; //length
void da(char *s, int m){
n++;
for(int i = 0; i < m; i++) c[i] = 0;
for(int i = 0; i < n; i++) c[x[i] = s[i]]++;
for(int i = 1; i < m; i++) c[i] += c[i - 1] ;
for(int i = n - 1; i >= 0; i--) sa[--c[x[i]]] = i;
for(int k = 1; k <= n; k <<= 1) {
int p = 0 ;
for(int i = n - k; i < n; i++) y[p++] = i ;
for(int i = 0; i < n; i++) if(sa[i] >= k) y[p++] =sa[i] - k;
for(int i = 0; i < m; i++) c[i] = 0;
for(int i = 0; i < n; i++) c[x[y[i]]]++;
for(int i = 1; i < m; i++) c[i] += c[i - 1];
for(int i = n - 1; i >= 0; i--) sa[--c[x[y[i]]]] = y[i] ;
swap(x , y); p = 1; x[sa[0]] = 0;
for(int i = 1; i < n; i++)
x[sa[i]] = y[sa[i - 1]] == y[sa[i]] && y[sa[i-1] + k] == y[sa[i] + k] ? p - 1 : p++;
if(p >= n) break ;
m = p;
}
n--;
int k = 0;
for(int i = 0; i <= n; i++) Rank[sa[i]] = i;
for(int i = 0; i < n; i++) {
if(k) k--;
int j = sa[Rank[i] - 1];
while(s[i + k] == s[j + k]) k++;
height[Rank[i]] = k;
}
}
ll count() {
ll ans = 0;
for(int i = 1; i <= n; i++) ans += n - sa[i] - height[i];
return ans;
}
void init() {
for(int i = 2; i < N; i++) lg[i] = lg[i >> 1] + 1;
for(int i = 2; i <= n; i++) f[i][0] = height[i];
for(int j = 1; j < 20; j++)
for(int i = 2; i + (1 << j) - 1 <= n; i++)
f[i][j] = min(f[i][j - 1], f[i + (1 << (j - 1))][j - 1]) ;
}
int get_lcp(int l, int r) {
if(Rank[l] > Rank[r]) swap(l, r);
l = Rank[l] + 1, r = Rank[r];
int k = lg[r - l + 1];
return min(f[l][k], f[r - (1 << k) + 1][k]);
}
}A, B;
int T;
int k;
char s[N], t[N];
int main() {
ios::sync_with_stdio(false); cin.tie(0);
cin >> T;
while(T--) {
cin >> k;
cin >> s;
int L = strlen(s);
if(k == 1) {
cout << 1ll * L * (L + 1) / 2 << '\n';
continue;
}
A.n = B.n = L;
for(int i = 0; i < L; i++) t[i] = s[L - i - 1];
t[L] = '\0';
A.da(s, 520); B.da(t, 520);
A.init(); B.init();
int last;
ll ans = 0;
// cout << B.get_lcp(0, 1) << '\n';
for(int j = 1; j < L; j++) {
last = 0;
for(int i = last + j; i < L; i += j) {
int r = i + A.get_lcp(last, i) - 1;
if(r >= i + j || r >= L) continue;
int l = last - B.get_lcp(L - i - 1, L - last - 1) + 1;
int len = r - l + 1;
ans += max(0, len - k * j + 1);
last = i;
}
}
cout << ans << '\n';
}
return 0;
}
题意:
两个人在一棵树上面博弈,每到一个结点第一个人可获得\(a_i\)的收益,第二个人可以得到\(b_i\)的收益。
第一个人确定起点过后,会一直走下去直到走不通,同一个结点不会经过两次。
问最后第一个人的分数与第二个人的分数之差为多少。
思路:
注意到只要起点确定,那么方案确定,答案也就确定了,所以最后我们要枚举所有的起点取\(max\)得到答案。
考虑起点固定的情况:可以利用树\(dp\)自底向下统计答案,维护最大、次大,最小、次小值。
之后考虑换根,我们需要知道往父亲结点方向走的最大最小值,这样才能更新当前的答案。
Code
#include
#define INF 0x3f3f3f3f3f3f3f3f
#define x first
#define y second
using namespace std;
typedef long long ll;
typedef pair pll;
const int N = 1e5 + 5;
int T, n;
int a[N];
int tot, head[N];
struct Edge{
int v, next;
}e[N << 1];
void adde(int u, int v) {
e[tot].v = v; e[tot].next = head[u]; head[u] = tot++;
}
pll f[N], g[N];
int d[N];
void dfs(int u, int fa) {
f[u].x = f[u].y = -INF;
g[u].x = g[u].y = INF;
for(int i = head[u]; i != -1; i = e[i].next) {
int v = e[i].v;
if(v == fa) continue;
dfs(v, u); d[u]++;
if(g[v].x + a[u] > f[u].x) {
f[u].y = f[u].x;
f[u].x = g[v].x + a[u];
} else if(g[v].x + a[u] > f[u].y) {
f[u].y = g[v].x + a[u];
}
if(f[v].x + a[u] < g[u].x) {
g[u].y = g[u].x;
g[u].x = f[v].x + a[u];
} else if(f[v].x + a[u] < g[u].y) {
g[u].y = f[v].x + a[u];
}
}
if(!d[u]) {
f[u].x = g[u].x = a[u];
}
}
ll p[N], q[N];
void dfs2(int u, int fa) {
for(int i = head[u]; i != -1; i = e[i].next) {
int v = e[i].v; ll r;
if(v == fa) continue;
if(d[u] == 1) p[v] = q[u] + a[v], q[v] = p[u] + a[v];
else {
r = (f[u].x == g[v].x + a[u]) ? f[u].y : f[u].x;
if(u != 1) r = max(r, q[u]); p[v] = r + a[v];
r = (g[u].x == f[v].x + a[u]) ? g[u].y : g[u].x;
if(u != 1) r = min(r, p[u]); q[v] = r + a[v];
}
dfs2(v, u);
}
}
int main() {
ios::sync_with_stdio(false); cin.tie(0);
cin >> T;
while(T--) {
cin >> n;
memset(head, -1, sizeof(head)); tot = 0;
for(int i = 1; i <= n; i++) cin >> a[i];
for(int i = 1; i <= n; i++) {
int k; cin >> k;
a[i] -= k; d[i] = 0;
}
for(int i = 1; i < n; i++) {
int u, v; cin >> u >> v;
adde(u, v); adde(v, u);
}
dfs(1, 0);
p[1] = q[1] = a[1];
dfs2(1, 0);
ll ans = g[1].x;
for(int i = 2; i <= n; i++) {
if(d[i]) ans = max(ans, min(p[i], g[i].x));
else ans = max(ans, p[i]);
}
cout << ans << '\n';
}
return 0;
}
题意:
给出一张图,每条边有一定权值,现在要求选择包含\(k\)个点的简单路径,问最长路径为多少。\(2\leq k\leq 6\)。
思路:
随机染色。
因为\(k\)范围很小,所以考虑随机给图上染上\(k\)种颜色。然后状压\(dp\)维护不同颜色的路径长度的最大值。
答案路径被染成不同颜色的概率为\(\frac{k!}{k^k}\),那么期望\(\frac{k^k}{k!}\)次染色即可把答案路径染为不同颜色。
Code
#include
using namespace std;
typedef long long ll;
const int N = 1e4 + 5;
mt19937 rnd(time(NULL));
int T, n, m, k;
int head[N], tot;
struct Edge{
int v, w, next;
}e[N << 1];
void adde(int u, int v, int w) {
e[tot].v = v; e[tot].w = w; e[tot].next = head[u]; head[u] = tot++;
}
int c[N];
int dp[64][N];
int main() {
ios::sync_with_stdio(false); cin.tie(0);
cin >> T;
while(T--) {
memset(head, -1, sizeof(head)); tot = 0;
cin >> n >> m >> k;
for(int i = 1; i <= m; i++) {
int u, v, w; cin >> u >> v >> w;
adde(u, v, w); adde(v, u, w);
}
int tot = 200;
int ans = 0;
while(tot--) {
memset(dp, -1, sizeof(dp));
for(int i = 1; i <= n; i++) {
c[i] = rnd() % k;
dp[1 << c[i]][i] = 0;
}
for(int S = 1; S < 1 << k; S++) {
for(int i = 1; i <= n; i++) {
if(S >> c[i] & 1) {
for(int p = head[i]; p != -1; p = e[p].next) {
int j = e[p].v;
if(dp[S ^ (1 << c[i])][j] >= 0)
dp[S][i] = max(dp[S][i], dp[S ^ (1 << c[i])][j] + e[p].w);
}
}
}
}
int e = (1 << k) - 1;
for(int i = 1; i <= n; i++) ans = max(ans, dp[e][i]);
}
if(ans == 0) cout << "impossible" << '\n';
else cout << ans << '\n';
}
return 0;
}
题意:
在二维平面上给出两个矩形,问平面存在多少区域。
思路:
考虑离散化点,那么平面区域即为\(5*5\)大小。然后暴力\(dfs\)求连通块即可。
小trick:左闭右开区间的形式可以降低代码复杂度。
Code
#include
#define y1 heyuhhh
using namespace std;
typedef long long ll;
int T;
int x[2][3], y[2][3];
int X[6], Y[6];
int mp[6][6];
bool vis[6][6];
int dx[4] = {-1, 1, 0, 0}, dy[4] = {0, 0, -1, 1};
bool ok(int x, int y, int z) {
return x >= 0 && y >= 0 && x <= 5 && y <= 5 && !vis[x][y] && mp[x][y] == z;
}
void dfs(int x, int y, int z){
vis[x][y] = 1;
for(int i = 0; i < 4; i++) {
int curx = x + dx[i], cury = y + dy[i];
if(ok(curx, cury, z)) dfs(curx, cury, z);
}
}
int main() {
ios::sync_with_stdio(false); cin.tie(0);
cin >> T;
while(T--) {
memset(vis, 0, sizeof(vis));
memset(mp, 0, sizeof(mp));
X[0] = Y[0] = 0;
for(int i = 0; i < 2; i++) {
cin >> x[i][0] >> y[i][0] >> x[i][1] >> y[i][1];
X[++X[0]] = x[i][0]; X[++X[0]] = x[i][1];
Y[++Y[0]] = y[i][0]; Y[++Y[0]] = y[i][1];
}
sort(X + 1, X + X[0] + 1); sort(Y + 1, Y + Y[0] + 1);
X[0] = unique(X + 1, X + X[0] + 1) - X - 1;
Y[0] = unique(Y + 1, Y + Y[0] + 1) - Y - 1;
for(int i = 0; i < 2; i++) {
x[i][0] = lower_bound(X + 1, X + X[0] + 1, x[i][0]) - X;
x[i][1] = lower_bound(X + 1, X + X[0] + 1, x[i][1]) - X;
y[i][0] = lower_bound(Y + 1, Y + Y[0] + 1, y[i][0]) - Y;
y[i][1] = lower_bound(Y + 1, Y + Y[0] + 1, y[i][1]) - Y;
}
for(int k = 0; k < 2; k++)
for(int i = x[k][0]; i < x[k][1]; i++)
for(int j = y[k][0]; j < y[k][1]; j++)
mp[i][j] |= (k + 1);
int res = 0;
for(int i = 0; i <= 5; i++) {
for(int j = 0; j <= 5; j++) {
if(!vis[i][j]) {
dfs(i, j, mp[i][j]);
res++;
}
}
}
cout << res << '\n';
}
return 0;
}
签到。
Code
#include
using namespace std;
typedef long long ll;
const int MAXN = 1e5 + 5, INF = 0x3f3f3f3f, MOD = 1e9 + 7;
const ll inf = 1000000000000000010LL, INFL = 0x3f3f3f3f3f3f3f3f;
#define lson o<<1,l,m
#define rson o<<1|1,m+1,r
#define mid l + ((r-l)>>1)
#define random(a,b) ((a)+rand()%((b)-(a)+1))
int t, n, d;
typedef double db;
const db eps = 1e-9;
struct node {
char s[11];
int p, t;
bool operator <(const node&rhs)const {
if (p == rhs.p)return t < rhs.t;
return p > rhs.p;
}
}a[MAXN];
inline int sign(db x) {
if (fabs(x) < eps)return 0;
return x < -eps ? -1 : 1;
}
inline int cmp(db a, db b) {
return sign(a - b);
}
int main() {
ios::sync_with_stdio(false); cin.tie(0);
cin >> t;
while (t--) {
cin >> n >> d;
for (int i = 1; i <= n; i++) {
cin >> a[i].s >> a[i].p >> a[i].t;
}
sort(a + 1, a + 1 + n);
db rate = n * d / 10.0;
if (cmp(rate - (int)rate, 0.5) == 0) {
cout << a[(int)rate + 1].s << '\n';
}
else {
cout << "Quailty is very great\n";
}
}
return 0;
}
题意:
有\(n\)个班级,每个班级有\(a_i\)个学生,同时制作了\(b_i\)份奶茶。现在问最多喝到奶茶的学生为多少,注意每个学生都不能喝他们自己班级制作的牛奶。
思路:
其实就是求最大匹配。
考虑\(hall\)定理的推论:
二分图的最大匹配\(|M|=|U|-max_{S\in U}\{|S|-|N(S)|\}\),其中\(N(S)\)表示与\(S\)相邻的点。
然后就可以愉快地分类讨论了:
- 最后答案如果只有一个班的学生喝了牛奶,那么只有可能是班上所有同学都喝了牛奶。因为增加学生不会改变\(|N(S)|\)。
- 最后答案如果有大于一个班的学生喝了牛奶,显然\(N(S)=V\),同时\(S=U\)才为最大匹配;答案即为\(|V|\).
- 剩下的情况显然\(max_{S\in U}\{|S|-|N(S)|\}=0\),答案即为\(|U|\)。
代码如下:
Code
#include
using namespace std;
typedef long long ll;
const int N = 1e6 + 6;
int T, n;
int a[N], b[N];
ll suma, sumb;
int main() {
ios::sync_with_stdio(false); cin.tie(0);
cin >> T;
while(T--) {
cin >> n;
suma = sumb = 0;
for(int i = 1; i <= n; i++) {
cin >> a[i] >> b[i];
suma += a[i]; sumb += b[i];
}
ll ans = min(suma, sumb);
for(int i = 1; i <= n; i++) ans = min(ans, suma - a[i] + sumb - b[i]);
cout << ans << '\n';
}
return 0;
}