2019杭电多校第二场补题

B: Beauty Of Unimodal Sequence(树状数组+贪心)

d p [ i ] [ 0 ] dp[i][0] dp[i][0]表示以 a [ i ] a[i] a[i]开头的最长递减子序列, d p [ i ] [ 1 ] dp[i][1] dp[i][1]表示以 a [ i ] a[i] a[i]开头的最长单峰子序列,这两个东西可以从后往前用树状数组 O ( n l o g n ) O(nlogn) O(nlogn)求出来。
这样我们得到了最长单峰子序列的长度,现在要求字典序最小/最大。
字典序最小容易想,直接贪心,从1到n,遇到可以放的就直接放。
字典序最大,倒着来一次字典序最小的步骤。也就是说,从后往前,能放就放。
为什么这样可以得到字典序最大呢,有没有可能出现 p 1 , p 2 , p 3 , p 4 p_1,p_2,p_3,p_4 p1,p2,p3,p4有两种选法: p 1 , p 4 p_1,p_4 p1,p4或者 p 2 , p 3 p_2,p_3 p2,p3,导致贪心得不到最大字典序呢,证明交给读者。
(其实就是因为我没能证出来)
ac代码:

#include<bits/stdc++.h>
#define ll long long
#define lowbit(x) (x&(-x))
using namespace std;
const int maxn = 3e5 + 50;
int n;
int cc[maxn];
int num = 0;
int a[maxn];
int p[maxn];
int dp[maxn][2], c0[maxn], c1[maxn];
void update(int i, int x, int* c){
    while(i < maxn) c[i] = max(c[i], x), i+=lowbit(i);return;
}
int query(int i, int *c){
    int res = 0;while(i) res = max(res, c[i]), i -= lowbit(i);return res;
}
void init()
{
    num = 0;
    for(int i = 1; i <= n; ++i) scanf("%d", &a[i]), cc[++num] = a[i];
    sort(cc+1, cc+1+num);
    num = unique(cc+1,cc+1+num) - cc - 1;
    for(int i = 1; i <= n; ++i) {
        a[i] = lower_bound(cc+1, cc+1+num, a[i])-cc;
    }
}
int ans[maxn], pos;
int sol(){
    for(int i = 0; i <= num+1; ++i) c0[i] = c1[i] = 0;
    int len = 0;
    pos = 0;
    for(int i = n; i > 0; --i){
        dp[i][0] = query(a[i]-1, c0) + 1;
        update(a[i], dp[i][0], c0);
        dp[i][1] = max(dp[i][0], query(num+1-a[i]-1, c1)+1);
        update(num+1-a[i], dp[i][1], c1);
        len = max(len, dp[i][1]);
    }
    int flag = 0;//是否在下坡
    for(int i = 1; i <= n; ++i){
        if(!pos && dp[i][1] == len){
            ans[++pos] = i;continue;
        }
        if(a[ans[pos]] == a[i]) continue;
        if(a[i] > a[ans[pos]] && !flag && dp[i][1] == len - pos){
            ans[++pos] = i;continue;
        }
        if(a[i] < a[ans[pos]] && dp[i][0] == len - pos){
            ans[++pos] = i;flag = 1;continue;
        }
    }
    return len;
}
int main()
{
	while(scanf("%d", &n) != EOF){
        init();
        int len = sol();
        for(int i = 1; i <= len; ++i) {
            printf("%d", ans[i]);
            if(i == len) printf("\n");else printf(" ");
        }
        reverse(a+1,a+1+n);
        len = sol();
        for(int i = len; i > 0; --i) {
            printf("%d", n - ans[i] + 1);
            if(i == 1) printf("\n");else printf(" ");
        }
	}
}

H:Harmonious Army(最小割)

网络流套路题。先把所有的价值都加上,那么对于一对关系的三个价值A,B,C,选择了价值A,等价于抛弃了价值B,C,即需要在答案中减去(B+C)。我们要让减去的值最小,问题转换为求最小代价。
在这里插入图片描述
来看题解的这张图,求一次最小割来把图片分成两边,分完后和s相连表示归属W阵营,和t相连表示归属M阵营。求完最小割意味着x和y划分好了阵营,所以我们需要给图上的边定义价值,让分割方法和阵营分配一一对应。
x和y都属于W阵营:
割掉了c和d,代价为B+C,也就是说:c+d = B+C
x和y都属于M阵营:
割掉了a和b,代价为A+B,也就是说:a+b = A+B
x和y属于不同阵营:
割掉a,d,e或者b,c,e,代价为A+C,也就是说a+d+e = b+c+e = A+C
可以求出e为固定值(A+C)/2 - B。a,b,c,d定下一个,其他就都定了。任取一个使得没有边权为负数的解,这里可以取a = b = (A+B)/2, c = d = (B+C)/2。
因为每个结点和源点/汇点的边可能不止一条,所以可以把这些边合并在一起来降低复杂度(不这样会TLE)。
因为边权可能不是整数,所以Dinic的板子可能要稍微改一下。
ac代码:

#include<iostream>
#include<cstdio>
#include<string.h>
#include<queue>
#include<cmath>
#define ll double
using namespace std;
const int maxn = 1e3 +50;
const ll inf = 1e12;
const ll eps = 1e-8;
struct node{
	int u,v,nxt;
	ll f;
}e[maxn*100];
int cnt = 0;
int head[maxn];
void add(int u,int v,ll f){
	e[cnt].u = u;
	e[cnt].v = v;
	e[cnt].f = f;
	e[cnt].nxt = head[u];
	head[u] = cnt++;

	e[cnt].u = v;
	e[cnt].v = u;
	e[cnt].f = 0;
	e[cnt].nxt = head[v];
	head[v] = cnt++;
}
int st,ed,ex;
int n, m;
long long in[maxn], out[maxn];
void init(){
	cnt = 0;
	st = n + 1, ex = ed = n + 2;
	memset(head,-1,(ex + 1)<<2);
	for(int i = 1; i <= n; ++i) in[i] = out[i] = 0;
}
int dep[maxn];
int q[maxn*2];
int tot,tail;
bool bfs(){
	memset(dep,-1,(ex+1)<<2);
	dep[st] = 1;
	q[tot = 0] = st,tail = 1;
	while(tot < tail){
		int u = q[tot++];
		if(u == ed) break;
		for(int i = head[u];~i;i = e[i].nxt){
			int v = e[i].v;
			if(dep[v]!=-1 || e[i].f < eps) continue;
			dep[v] = dep[u] + 1;
			q[tail++] = v;
		}
	}
	return dep[ed]!=-1;
}
int cur[maxn];
ll dfs(int u,ll flow){
	ll res = flow;
	if(u == ed) return flow;
	for(int &i = cur[u];~i;i = e[i].nxt){
		int v = e[i].v;
		if(dep[v]!=dep[u] + 1 || e[i].f < eps) continue;
		ll d = dfs(v,min(res,e[i].f));
		e[i].f -= d;
		e[i^1].f += d;
		res -= d;
		if(res < eps) break;
	}
	if(flow - res < eps) dep[u] = -1;//·ÀÖ¹ÖØÐÂËÑË÷
	return flow - res;
}
ll dinic(){
	ll ans = 0;
	ll d;
	while(bfs()){
		for(int i = 0;i <= ex;++i) cur[i] = head[i];
		while((d = dfs(st,inf)) > 0.0) {//double×¢Òâ
            ans += d;
		}
	}
	return ans;
}
void sol(){
	ll ans = 0.0;
	while(m--){
        int u, v, a, b, c;
        scanf("%d%d%d%d%d", &u, &v, &a, &b, &c);
        in[u] += a+b;
        in[v] += a+b;
        add(u, v, (a+c)/2.0 - b);
        add(v, u, (a+c)/2.0 - b);
        out[u] += b+c;
        out[v] += b+c;
        ans += a+b+c;
	}
	for(int i = 1; i <= n; ++i){
        add(st, i, in[i]/2.0);
        add(i, ed, out[i]/2.0);
	}
    long long res = round(ans - dinic());
	printf("%lld\n", res);
}
int main(){
	while(~scanf("%d%d", &n, &m)){
		init();sol();
	}
}

L:Longest Subarray(线段树+思维)

对每个元素单独考虑。当右端点固定为r的时候,对于一个值为x的元素,它的左端点有两段可行区间,一段是[l,r]没有x,一段是[l,r]有超过k个x。右端点右移的时候维护每个元素的左端点可行区间。
具体维护方法是:右端点从r-1移动到r,对除了a[r]之外所有元素来说r这个点都可以成为可行左端点,而对于a[r]来说,设它上次出现的位置是t,那么本来[t,r-1]都是它的可行左端点区间,现在不是了。同时如果a[r]出现次数达到了k次或以上,左边会多出一段可行区间。
线段树区间加法维护每个点可作为多少元素的左端点。对每个右端点,查询最小的等于c的位置。

#include<bits/stdc++.h>
#define ll long long
#define mid ((l+r)>>1)
#define lson rt<<1, l, mid
#define rson rt<<1|1, mid+1, r
using namespace std;
const int maxn = 1e5 + 50;
int n, c, k;
int a[maxn];
int lz[maxn<<2], mx[maxn<<2];
void build(int rt, int l, int r){
    mx[rt] = lz[rt] = 0;
    if(l == r) return;
    build(lson);build(rson);
}
void init(){
    for(int i = 1; i <= n; ++i) scanf("%d", &a[i]);
    build(1, 1, n);
}
void down(int rt){
    if(!lz[rt]) return;
    lz[rt<<1] += lz[rt];
    lz[rt<<1|1] += lz[rt];
    mx[rt<<1] += lz[rt];
    mx[rt<<1|1] += lz[rt];
    lz[rt] = 0;return;
}
void up(int rt){
    mx[rt] = max(mx[rt<<1], mx[rt<<1|1]);
}
void add(int rt, int l, int r, int L, int R, int x){
    if(L <= l && r <= R){
        lz[rt] += x;
        mx[rt] += x;
        return;
    }
    down(rt);
    if(L <= mid) add(lson, L, R, x);
    if(R > mid) add(rson, L, R, x);
    up(rt);
}
int query(int rt, int l, int r){
    if(mx[rt] < c) return -1;
    if(l == r) return l;
    down(rt);
    if(mx[rt<<1] >= c) return query(lson);
    return query(rson);
}
queue<int> q[maxn];//q[i]存最后k个i出现的位置
int last[maxn];//last[i]存最后一个i出现的位置
void sol()
{
    if(k == 1) {
        printf("%d\n",n);return;
    }
    for(int i = 1; i <= c; ++i) {
        while(q[i].size()) q[i].pop();
        last[i] = 0;
    }
    int ans = 0;
    for(int i = 1; i <= n; ++i){
        int x = a[i];
        add(1, 1, n, i, i, c);
        add(1, 1, n, last[x]+1, i, -1);//元素x的可行左端点
        last[x] = i;
        q[x].push(i);
        if(q[x].size() == k){//第一次出现k个
            int l = 1, r = q[x].front();
            add(1, 1, n, l, r, 1);
        }
        if(q[x].size() > k){
            int l = q[x].front()+1; q[x].pop();
            int r = q[x].front();
            add(1, 1, n, l, r, 1);
        }
        int t = query(1, 1, n);
        if(t != -1) {
            ans = max(ans, i - t + 1);
        }
    }
    printf("%d\n", ans);
}
int main()
{
	while(scanf("%d%d%d", &n, &c, &k)!=EOF){
        init();sol();
	}
}
/*
7 4 2
2 1 4 1 4 3 2
*/

I:I Love Palindrome String

这题需要前置知识:回文树
学习回文树之前最好先学AC自动机,比较有利于理解fail指针。
回文树可以求出所有本质不同的回文串,可以记录它们第一次出现的位置和出现次数,由于最多有n个本质不同的回文串,所以我们只要遍历这些回文串,判断该回文串的左半部分是否也是回文串(可以用马拉车或者哈希判断)。如果是,答案就加上这个回文串出现的次数。
ac代码:

#include<bits/stdc++.h>
#define ll long long
#define ull unsigned long long
using namespace std;
const int maxn =3e5 + 50;
int n;
ull sed = 133, ha1[maxn], ha2[maxn], pp[maxn];
struct PT{
    int last, len[maxn], fail[maxn], cnt[maxn], pos[maxn], ch[maxn][26], tot;
    void init(){
        last = tot = 1;
        len[0] = 0, len[1] = -1;
        fail[0] = fail[1] = 1;
        cnt[0] = cnt[1] = 0;
        memset(ch[0], 0, sizeof ch[0]);
        memset(ch[1], 0, sizeof ch[0]);
    }
    void insert(char *s, int i){
        int x = s[i] - 'a';
        int v = last;
        while(s[i] != s[i - len[v] - 1]) v = fail[v];
        if(!ch[v][x]){
            int u = ++tot;
            int k = fail[v];
            while(s[i] != s[i - len[k] - 1]) k = fail[k];
            fail[u] = ch[k][x];
            len[u] = len[v] + 2;
            pos[u] = i;
            cnt[u] = 0;
            memset(ch[u], 0, sizeof ch[u]);
            ch[v][x] = u;
        }
        last = ch[v][x];//是v的ch
        cnt[last]++;
    }
    void Count(){
        for(int i = tot; i; --i) cnt[fail[i]]+=cnt[i];//大的包小的
    }
}pt;
int ans[maxn];
char s[maxn];
ull gethash1(int l, int r){return ha1[r] - ha1[l-1]*pp[r-l+1];}
ull gethash2(int l, int r){return ha2[l] - ha2[r+1]*pp[r-l+1];}
bool check(int l, int r){
    return gethash1(l, (l+r)/2) == gethash2((l+r+1)/2, r);
}
void sol()
{
    ha1[0] = ha2[n+1] = 0;
    for(int i = 1;i <= n; ++i) ha1[i] = (s[i] - 'a') + ha1[i-1]*sed;
    for(int i = n; i > 0; --i) ha2[i] = (s[i] - 'a') + ha2[i+1]*sed;
    pt.init();
    for(int i = 1; i <= n; ++i) pt.insert(s, i), ans[i] = 0;
    pt.Count();
    for(int i = 2; i <= pt.tot; ++i){
        int r = pt.pos[i];
        int l = r - pt.len[i] + 1;
        r = (l+r)/2;
        if(check(l, r)) ans[pt.len[i]] += pt.cnt[i];
    }
    for(int i = 1; i <= n; ++i) {
        if(i > 1) printf(" ");
        printf("%d", ans[i]);
    }printf("\n");
}
int main()
{
    pp[0] = 1;
    for(int i = 1; i < maxn; ++i) pp[i] = pp[i-1]*sed;
	while(scanf("%s", s+1)!=EOF){
        n = strlen(s+1);
        sol();
	}
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值