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();
}
}