AGC 杂题

AGC029C Lexicographic constraints

\(n\) 个字符串,现在告知它们的长度 \(a_i\),求使得 \(\forall i\in[1,n),s_i<s_{i+1}\) 的最小字符集大小。

\(n\le 2\times 10^5,a_i\le 10^9\)

二分字符集大小 \(|\Sigma|\),分类讨论,设起始字符为 a

  • \(a_i<a_{i+1}\):显然 \(s_{i+1}\leftarrow s_i+(a_{i+1}-a_{i})\times \texttt{a}\) 是最优的;
  • \(a_i\ge a_{i+1}\):显然 \(s_{i+1}\)\(s_i\) 长为 \(a_{i+1}\) 的前缀并将最末一位进一是最优的,注意若第一位产生了进位则这个 \(|\Sigma|\) 不合法。

由于 \(a_i\le 10^9\),所以我们并不能开字符串模拟。但是考虑到真正不是 a 的位置只有 \(O(n)\) 数量级,于是我们只要用栈维护这些位置即可。时间复杂度 \(O(n\log n)\)

点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int maxn=2e5+5;
struct Node {
	int v, c;

	Node() {}
	Node(int v, int c):v(v), c(c) {}
};

int n;
int top;
int a[200010];
Node sta[200010];

inline void insert(int v, int x) {
	while(sta[top].v > v) --top;
	if(sta[top].v == v) sta[top].c++;
	else sta[++top] = Node(v, 1);
	if(top > 1 && sta[top].c == x) --top, insert(v - 1, x);
}

inline int chk(int x) {
	sta[top = 1] = Node(0, 0);
	for(int i = 2; i <= n; i++)
		if(a[i] <= a[i - 1]) insert(a[i], x);
	return sta[1].c == 0;
}

int main() {
	cin >> n;
	for(int i = 1; i <= n; i++) cin >> a[i];

	int ct = 0;
	for(int i = 2; i <= n; i++) ct += a[i] > a[i - 1];
	if(ct == n - 1) return puts("1"), 0;
    
	int l = 2, r = n, res = 1;
	while(l <= r) {
		int mid = (l + r) >> 1;
		if(chk(mid)) res = mid, r = mid - 1;
		else l = mid + 1;
	} printf("%d\n", res);
	return 0;
}

AGC032E Modulo Pairing

给你 \(2n\) 个数,将数两两求和并模 \(m\),求得到的 \(n\) 个数中最大值的最小值。

\(n\le 10^5,a_i<m\le 10^9\)

先对 \(a\) 排序,对于每一对 \((i,j)\),把 \(i,j\) 用一条线连起来。

如果 \(a_i+a_j<M\),则用蓝色线,如果 \(a_i+a_j≥M\),则用红色线。

那么我们可以证明,一定存在一种最优情况,满足:

  • 存在一个分界点,使得它左右两侧没有匹配,也就是没有连线经过分界点。
  • 只考虑分界点左侧,则最小的数和最大的数连线,第二小的数和第二大的数连线,以此类推。
  • 分界点右侧也是一样,最小的和最大的连线。
  • 分界点左侧的线的颜色都是蓝色,分界点右侧的线的颜色都是红色。

用图表示就是这样:

怎么证明它是对的呢?可以使用调整法。

考虑两个数对,如果它们同色但不包含,或者它们异色但不相离,则可以调整成满足条件且不更劣的情况。如图:

二分分割点即可。时间复杂度 \(O(n\log n)\)

点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int maxn=2e5+3;
int n,m,a[maxn];
bool check(int x){
    for(int i=x*2+1,j=2*n;i<j;i++,j--)
        if(a[i]+a[j]<m) return 0;
    return 1;
}
signed main(){
    cin>>n>>m;
    for(int i=1;i<=2*n;i++) cin>>a[i];
    sort(a+1,a+2*n+1);
    int l=0,r=n,pos=0;
    while(l<=r){
        int mid=(l+r)>>1;
        if(check(mid)) r=mid-1,pos=mid*2;
        else l=mid+1;
    }
    int ans=0;
    for(int i=1,j=pos;i<j;i++,j--)
        ans=max(ans,a[i]+a[j]);
    for(int i=pos+1,j=2*n;i<j;i++,j--)
        ans=max(ans,(a[i]+a[j])-m);
    cout<<ans;
    return 0;
}

AGC032D Rotation Sort

给你一个排列 \(a\),可以执行以下操作任意次:

  • 选定一个区间 \([l,r]\),将 \(a_l\) 放到 \(a_r\) 之后,花费为 \(A\)
  • 选定一个区间 \([l,r]\),将 \(a_r\) 放到 \(a_l\) 之前,花费为 \(B\)

求将排列变为升序的最小花费。

\(n\le 5000\)

显然最多每个数操作一次。分为三类:向前放,不动,向后放。其中不动的元素构成上升子序列。借此可以设计转移,设 \(f(i,j)\) 表示当前在 \(i\),最后一个不动的值\(j\),则有转移:

\[f(i,j)=\begin{cases} f(i-1,j)+A&j<a_i\\ \min_{k=1}^{j-1}f(i-1,k)& j=a_i\\ f(i-1,j)+B&j>a_i \end{cases}\]

记一个前缀 \(\min\),时间复杂度 \(O(n^2)\),可过。

但是你看这貌似可以上线段树?情况 1,3 相当于区间加。情况 2 相当于区间 \(\min\) + 单点修改,综上时间复杂度 \(O(n\log n)\)

点击查看代码
#include<bits/stdc++.h>
#define int long long
#define ls (now<<1)
#define rs (now<<1|1)
using namespace std;
const int maxn=5e3+3;
int n,A,B,a[maxn];
int tag[maxn<<2],mi[maxn<<2];
void pushup(int now){
    mi[now]=min(mi[ls],mi[rs]);
}
void pushdown(int now){
    if(tag[now]){
        mi[ls]+=tag[now];
        mi[rs]+=tag[now];
        tag[ls]+=tag[now];
        tag[rs]+=tag[now];
        tag[now]=0;
    }
}
void build(int now,int l,int r){
    if(l==r){
        if(l<a[1]) mi[now]=A;
        else if(l==a[1]) mi[now]=0;
        else mi[now]=B;
        return;
    }
    int mid=(l+r)>>1;
    build(ls,l,mid); build(rs,mid+1,r);
    pushup(now);
}
void modify(int now,int l,int r,int L,int R,int x){
    if(L<=l&&r<=R){
        mi[now]+=x;
        tag[now]+=x;
        return;
    }
    pushdown(now);
    int mid=(l+r)>>1;
    if(L<=mid) modify(ls,l,mid,L,R,x);
    if(mid+1<=R) modify(rs,mid+1,r,L,R,x);
    pushup(now);
}
void modify(int now,int l,int r,int pos,int x){
    if(l==r){
        mi[now]=x;
        return;
    }
    pushdown(now);
    int mid=(l+r)>>1;
    if(pos<=mid) modify(ls,l,mid,pos,x);
    else modify(rs,mid+1,r,pos,x);
    pushup(now);
}
int query(int now,int l,int r,int L,int R){
    if(L<=l&&r<=R){
        return mi[now];
    }
    pushdown(now);
    int mid=(l+r)>>1,res=0x3f3f3f3f3f3f3f3f;
    if(L<=mid) res=min(res,query(ls,l,mid,L,R));
    if(mid+1<=R) res=min(res,query(rs,mid+1,r,L,R));
    return res;
}
signed main(){
    cin>>n>>A>>B;
    for(int i=1;i<=n;i++) cin>>a[i];
    build(1,1,n);
    for(int i=2;i<=n;i++){
        if(a[i]>1) modify(1,1,n,a[i],query(1,1,n,1,a[i]-1));
        if(a[i]>1) modify(1,1,n,1,a[i]-1,A);
        if(a[i]<n) modify(1,1,n,a[i]+1,n,B);
    }
    int ans=0x3f3f3f3f3f3f3f3f;
    for(int i=1;i<=n;i++) ans=min(ans,query(1,1,n,i,i));
    cout<<ans;
    return 0;
}
posted @ 2024-11-01 15:25  view3937  阅读(6)  评论(0编辑  收藏  举报
Title