题意:选择最长的两个子区间或者一个子区间,要求总长度最长,且区间内每个数最多只出现一次。
解法:设 为区间 内最长的合法子区间,预处理一下这个dp,我们从小到大枚举 ,然后从 到1枚举 ,一开始我们用一个multiset存 ,现在假设我加入了 ,我们找到 出现的位置 ,假设 当前所在的区间为 ,那么我们把 从set中删除,set再插入 ,然后找到set中的最大值 + i - j + 1去和答案取max即可。
#include<bits/stdc++.h>
#define ls o * 2
#define rs o * 2 + 1
#define mid (l + r) / 2
using namespace std;
const int maxn = 1005;
int vis[maxn], a[maxn], b[maxn], Case;
vector<int> G[maxn];
int cat[maxn * 4][2], d[maxn][maxn];
void pushdown(int o, int p) {
if (cat[o][p]) {
cat[ls][p] = cat[rs][p] = cat[o][p];
cat[o][p] = 0;
}
}
void up(int o, int l, int r, int ql, int qr, int v, int p) {
if (ql > qr)
return;
if (l >= ql && r <= qr) {
cat[o][p] = v;
return;
}
pushdown(o, p);
if (ql <= mid)
up(ls, l, mid, ql, qr, v, p);
if (qr > mid)
up(rs, mid + 1, r, ql, qr, v, p);
}
int qu(int o, int l, int r, int k, int p) {
if (l == r)
return cat[o][p];
pushdown(o, p);
if (k <= mid)
return qu(ls, l, mid, k, p);
return qu(rs, mid + 1, r, k, p);
}
multiset<int> s;
void solve() {
int n;
scanf("%d", &n);
for (int i = 1; i <= n; i++)
scanf("%d", &a[i]), b[i] = a[i];
sort(b + 1, b + 1 + n);
int m = unique(b + 1, b + 1 + n) - b - 1;
for (int i = 1; i <= m; i++)
G[i].clear();
for (int i = 1; i <= n; i++) {
a[i] = lower_bound(b + 1, b + 1 + m, a[i]) - b;
G[a[i]].push_back(i);
}
for (int i = 1; i <= n; i++) {
int j = i;
memset(vis, 0, sizeof(vis));
for (; j <= n; j++)
if (!vis[a[j]])
d[i][j] = j - i + 1, vis[a[j]] = 1;
else
break;
for (; j <= n; j++)
d[i][j] = d[i][j -1];
}
for (int k = 2; k <= n; k++)
for (int i = 1; i + k - 1 <= n; i++) {
int j = i + k - 1;
d[i][j] = max(d[i][j], max(d[i + 1][j], d[i][j - 1]));
}
int ans = 1;
for (int i = 1; i <= n; i++) {
up(1, 1, n, 1, n, 1, 0);
up(1, 1, n, 1, n, i, 1);
memset(vis, 0, sizeof(vis));
s.clear();
s.insert(d[1][i]);
s.insert(0);
for (int j = i; j; j--)
if (!vis[a[j]]) {
vis[a[j]] = 1;
for (auto k : G[a[j]])
if (k <= i) {
int l = qu(1, 1, n, k, 0);
int r = qu(1, 1, n, k, 1);
up(1, 1, n, l, k - 1, k - 1, 1);
up(1, 1, n, k + 1, r, k + 1, 0);
auto it = s.find(d[l][r]);
if (it != s.end())
s.erase(it);
s.insert(d[l][k - 1]);
s.insert(d[k + 1][r]);
}
auto it = --s.end();
ans = max(ans, i - j + 1 + (*it));
}
else
break;
}
printf("Case #%d: %d\n", ++Case, ans);
}
int main() {
int T;
scanf("%d", &T);
while (T--)
solve();
}
题意:有 个队伍,每个队伍有赔率 如果给队伍 投资了 元,并且该队伍如果获胜了,就会回报 元,如果输了,那么钱打水漂了,问最多能投资多少个队,使得只要有一个队获胜,就能赚钱。
解法:当所有队伍获胜的回报是一样的,才是最佳的决策,假设总投资是1,那么队伍 的投资应该是 ,因此我们可以给所有队伍按照上式从小到大排序,然后取前 个队伍使得 刚好小于1,那么 就是答案
#include<bits/stdc++.h>
#define db double
using namespace std;
const int maxn = 105;
long double a[maxn], A, B;
int Case;
void solve() {
int n;
char c;
scanf("%d", &n);
for (int i = 1; i <= n; i++) {
cin>>A>>c>>B;
a[i] = A / (A + B);
}
long double sum = 0.0;
int ans = 0;
sort(a + 1, a + 1 + n);
for (int i = 1; i <= n; i++) {
sum += a[i];
if (sum >= 1.0)
break;
ans++;
}
printf("Case #%d: %d\n", ++Case, ans);
}
int main() {
int T;
scanf("%d", &T);
while (T--)
solve();
}
F.Mr. Panda and Fantastic Beasts
题意:求第一个串的最短的且字典序最小的子串,使得该子串不是别的串的子串。
解法:把所有串连接建后缀数组, 表预处理 数组,假设第一个串长度为T,枚举 ,如果 ,那么找到最大的 ,最小的 ,使得 ,预处理完后,枚举第一个串的位置 ,找到对应的 ,用st表求出区间 和 的 ,两个区间取max,就知道了后缀 与其他串的 长度,然后去更新答案即可。
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn = 4e5 + 10, inf = 1e9;
int s[maxn], sa[maxn], t[maxn], t2[maxn], c[maxn], n;
int Case;
void build(int m) {
int i,*x=t,*y=t2;
for(i=0;i<m;i++)c[i]=0;
for(i=0;i<n;i++)c[x[i]=s[i]]++;
for(i=1;i<m;i++)c[i]+=c[i-1];
for(i=n-1;i>=0;i--)sa[--c[x[i]]]=i;
for(int k=1;k<=n;k<<=1){
int p=0;
for(i=n-k;i<n;i++)y[p++]=i;
for(i=0;i<n;i++)if(sa[i]>=k)y[p++]=sa[i]-k;
for(i=0;i<m;i++)c[i]=0;
for(i=0;i<n;i++)c[x[y[i]]]++;
for(i=0;i<m;i++)c[i]+=c[i-1];
for(i=n-1;i>=0;i--)sa[--c[x[y[i]]]]=y[i];
swap(x,y);
p=1;x[sa[0]]=0;
for(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;
}
}
int Rank[maxn],height[maxn], d[maxn][21], T, L[maxn], R[maxn];
char str[maxn], str2[maxn];
void getHeight(){
int i,j,k=0;
for(i=0;i<n;i++)Rank[sa[i]]=i;
for(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;
}
for (int i = 0; i < n; i++)
d[i][0] = height[i];
for (int i = 1; (1 << i) < n; i++)
for (int j = 0; j - 1 + (1 << i) < n; j++)
d[j][i] = min(d[j][i - 1], d[j + (1 << i - 1)][i - 1]);
int cat = 0;
for (int i = 0; i < n; i++) {
if (sa[i] <= T)
L[i] = cat;
else
cat = i;
}
cat = 0;
for (int i = n - 1; ~i; i--) {
if (sa[i] <= T)
R[i] = cat;
else
cat = i;
}
}
int rmq(int l, int r) {
if (l > r)
return inf;
int k = log2(r - l + 1);
return min(d[l][k], d[r - (1 << k) + 1][k]);
}
int solve() {
int m, k, ans = inf, p = 0;
n = 0;
scanf("%d", &m);
scanf("%s", str);
for (int i = 0; str[i]; i++)
s[n++] = str[i] - 'a' + 2;
T = n - 1;
while (--m) {
scanf("%s", str2);
s[n++] = 1;
for (int i = 0; str2[i]; i++)
s[n++] = str2[i] - 'a' + 2;
}
s[n++] = 0;
build(30);
getHeight();
printf("Case #%d: ", ++Case);
for (int i = 0; i <= T; i++) {
int pos = Rank[i];
int l = L[pos], r = R[pos];
int res = inf;
int t1 = rmq(l + 1, pos);
int t2 = rmq(pos + 1, r);
if (t1 == inf)
res = min(res, t2);
else if (t2 == inf)
res = min(res, t1);
else
res = max(t1, t2);
if (res < T - i + 1) {
if (res + 1 < ans || (res + 1 == ans && (ans == inf || Rank[i] < Rank[p]))) {
p = i;
ans = res + 1;
}
}
}
if (ans == inf)
return puts("Impossible"), 0;
for (int i = 0; i < ans; i++)
printf("%c", str[p + i]);
puts("");
}
int main() {
int T;
scanf("%d", &T);
while (T--)
solve();
}
题意:给一个带有边权的树,每个点有颜色,有1e5次询问,每次询问从x出发,只能经过边权不超过y的的边,问能走到的点中,哪个颜色最多,强制在线
解法:克鲁斯卡尔重构树一眼题,前几天学的,今天就比赛就遇到了,可惜没读题,我们对每个点根据颜色建立动态开点线段树,然后边建立克鲁斯卡尔重构树边线段树合并,同时用一个数组记录当前子树最多的颜色,然后每次询问,用倍增算法找到最大的点权<=y的点,然后输出预存的该点的答案即可。
#include<bits/stdc++.h>
#define mid (l + r) / 2
using namespace std;
const int maxn = 2e5 + 10;
int Case;
int p[maxn], rt[maxn], ls[maxn * 21], rs[maxn * 21], val[maxn];
int mx[maxn * 21], cat[maxn * 21], ans[maxn], f[maxn][21], cnt;
struct node {
int u, v, w;
bool operator<(const node& t) const {
return w < t.w;
}
} E[maxn];
int find(int x) {
if (p[x] != x)
p[x] = find(p[x]);
return p[x];
}
void pushup(int o) {
mx[o] = max(mx[ls[o]], mx[rs[o]]);
if (mx[ls[o]] == mx[o])
cat[o] = cat[ls[o]];
else
cat[o] = cat[rs[o]];
}
void up(int &o, int l, int r, int k) {
if (!o)
o = ++cnt;
if (l == r) {
mx[o]++;
cat[o] = l;
return;
}
if (k <= mid)
up(ls[o], l, mid, k);
else
up(rs[o], mid + 1, r, k);
pushup(o);
}
int merge(int &o, int pre, int l, int r) {
if (!o)
return pre;
if (!pre)
return o;
if (l == r) {
mx[o] += mx[pre];
return o;
}
ls[o] = merge(ls[o], ls[pre], l, mid);
rs[o] = merge(rs[o], rs[pre], mid + 1, r);
pushup(o);
return o;
}
void solve() {
for (int i = 1; i <= cnt; i++)
ls[i] = rs[i] = mx[i] = 0;
cnt = 0;
printf("Case #%d:\n", ++Case);
int n, m, u, v, w, x, q, tot = 0, pre = 0;
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; i++) {
p[i] = ++tot;
rt[i] = val[i] = 0;
scanf("%d", &x);
ans[i] = x;
up(rt[i], 1, n, x);
}
for (int i = 1; i <= m; i++)
scanf("%d%d%d", &E[i].u, &E[i].v, &E[i].w);
sort(E + 1, E + 1 + m);
for (int i = 1; i <= m; i++) {
u = find(E[i].u);
v = find(E[i].v);
if (u == v)
continue;
p[++tot] = tot;
rt[tot] = merge(rt[u], rt[v], 1, n);
ans[tot] = cat[rt[tot]];
val[tot] = E[i].w;
p[u] = p[v] = tot;
f[u][0] = f[v][0] = tot;
f[tot][0] = 0;
}
for (int i = 1; i <= 20; i++)
for (int j = 1; j <= tot; j++)
f[j][i] = f[f[j][i - 1]][i - 1];
scanf("%d", &q);
while (q--) {
scanf("%d%d", &u, &w);
u ^= ans[pre];
w ^= ans[pre];
for (int i = 20; ~i; i--)
if (f[u][i] && val[f[u][i]] <= w)
u = f[u][i];
printf("%d\n", ans[pre = u]);
}
}
int main() {
int T;
scanf("%d", &T);
while (T--)
solve();
}