2016 ACM-ICPC CHINA-Final

C.Mr. Panda and Strips

题意:选择最长的两个子区间或者一个子区间,要求总长度最长,且区间内每个数最多只出现一次。
解法:设 d p [ i ] [ j ] dp[i][j] 为区间 [ i , j ] [i, j] 内最长的合法子区间,预处理一下这个dp,我们从小到大枚举 i i ,然后从 i i 到1枚举 j j ,一开始我们用一个multiset存 d p [ 1 ] [ i ] dp[1][i] ,现在假设我加入了 a i a_{i} ,我们找到 a i a_{i} 出现的位置 k k ,假设 k k 当前所在的区间为 [ p , q ] [p, q] ,那么我们把 d p [ p ] [ q ] dp[p][q] 从set中删除,set再插入 d p [ p ] [ k 1 ] , d p [ k + 1 ] [ q ] dp[p][k-1], dp[k+1][q] ,然后找到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();
}

E.Bet

题意:有 n n 个队伍,每个队伍有赔率 A i : B i A_{i}:B_{i} 如果给队伍 i i 投资了 x x 元,并且该队伍如果获胜了,就会回报 x ( B i + A i ) / A i x * (B_{i} + A_{i})/A_{i} 元,如果输了,那么钱打水漂了,问最多能投资多少个队,使得只要有一个队获胜,就能赚钱。
解法:当所有队伍获胜的回报是一样的,才是最佳的决策,假设总投资是1,那么队伍 i i 的投资应该是 A i / ( A i + B i ) A_{i}/(A_{i}+B_{i}) ,因此我们可以给所有队伍按照上式从小到大排序,然后取前 k k 个队伍使得 i = 1 k A i / ( A i + B i ) \sum_{i=1}^{k}A_{i}/(A_{i}+B_{i}) 刚好小于1,那么 k k 就是答案
#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

题意:求第一个串的最短的且字典序最小的子串,使得该子串不是别的串的子串。
解法:把所有串连接建后缀数组, s t st 表预处理 h e i g h t height 数组,假设第一个串长度为T,枚举 s a [ i ] sa[i] ,如果 s a [ i ] < = T sa[i]<=T ,那么找到最大的 p p ,最小的 q q ,使得 s a [ p ] > T s a [ q ] > T sa[p]>T,sa[q]>T ,预处理完后,枚举第一个串的位置 i i ,找到对应的 p , q p, q ,用st表求出区间 [ p + 1 , r a n k [ i ] ] [p+1, rank[i]] [ r a n k [ i ] + 1 , q ] [rank[i]+1,q] l c p lcp ,两个区间取max,就知道了后缀 [ i , T ] [i, T] 与其他串的 l c p lcp 长度,然后去更新答案即可。
#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();
}

G.Pandaria

题意:给一个带有边权的树,每个点有颜色,有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();
}
发布了302 篇原创文章 · 获赞 98 · 访问量 7万+

猜你喜欢

转载自blog.csdn.net/ccsu_cat/article/details/102228858