Codeforces Round 905 Div 1 (CF1887)

A1. Dances (Easy version)

\(a,b\) 序列都从小到大排序,\(a\) 贪心删大的,\(b\) 贪心删小的,二分答案并 \(O(n)\) \(\text{check}\)

Code ```cpp const int N=1e5+5; int T,n,m; int a[N],b[N]; il bool check(int mid) { for(int i=1,j=mid+1;i<=n-mid;i++,j++) if(a[i]>=b[j]) return 0; return 1; } int main() { T=read(); while(T--) { n=read(),m=read(); a[1]=1; for(int i=2;i<=n;i++) a[i]=read(); for(int i=1;i<=n;i++) b[i]=read(); sort(a+1,a+n+1),sort(b+1,b+n+1); int l=0,r=n; while(l >1; if(check(mid)) r=mid; else l=mid+1; } printf("%d\n",l); } return 0; } ```

A2. Dances (Hard Version)

日常给大家提供乐子。

Solution 1

现在的限制变成了 \(m\le 10^9\),肯定不能一个一个求答案。但是发现我们只关心这个 \(a_1\)\(a,b\) 中其余的每个元素的大小关系。也就是说我们只要枚举 \(4\times 10^5\) 种大小关系,对他们分别求解就可以得到所有答案了。
原来对一个给定序列求答案的时间复杂度是 \(O(n\log n)\),考虑优化这个过程。

同样二分删掉的数量 \(mid\),设 \(a_1=x\)\(a\) 序列排序时不包含 \(a_1\)
那么把 \(x\) 插进 \(a\) 的对应位置,需要判断的区间可以分成 \(3\) 段:\(a\) 序列中在 \(x\) 前的部分,\(x\) 的位置,\(a\) 序列中在 \(x\) 后的部分。

已经有两个 \(\log\) 了,需要 \(O(1)\) 地判断 \([a_l,a_r]\)\([b_L,b_R]\) 对应起来是否合法。
\(lst_i\) 表示第一个比 \(b_i\) 小的位置与当前位置的差值。换句话说,取最大的 \(j\) 使 \(a_j,令 \(lst_i= j-i\)。那么 \([a_l,a_r]\)\([b_L,b_R]\) 合法的判断条件就是 \(L-l\le \min\{lst_L,\dots,lst_R\}\)。使用 ST 表维护。

时间复杂度 \(O(n \log^2 n)\)
我也不知道我怎么 5min 想到这个做法调了 1h 的,所以写出来给大家看看乐子。

Solution 2

发现答案只有两种,我们先把 \(a_{2,\dots,n}\)\(b\) 匹配。
现在加了一个数,要么以上匹配还是合法,要么变不合法了,那就把新加的那个删掉,答案增加 \(1\)。于是就做完了。

Code ``` #define int long long const int N=2e5+5; int T,n,m; int a[N],b[N],d[N],L[N],tot; int st[20][N]; il bool check(int l,int r,int L,int R) { if(l>r) return 1; int len=__lg(R-L+1); int mn=min(st[len][L],st[len][R-(1< n-mid&&mid) return check(1,n-mid,mid+1,n); bool flag1=check(1,pos-1,mid+1,mid+pos-1); bool flag2=x >1; if(Check(mid,x)) r=mid; else l=mid+1; } return l; } void solve() { tot=1; n=read(),m=read(); d[1]=m; d[++tot]=1; for(int i=1;i 1) ans+=(d[i+1]-d[i]-1)*Solve(d[i]+1); } printf("%lld\n",ans); } signed main() { int T=read(); while(T--) solve(); } ```

B. Time Travel

因为时间和时间不好区分,我们不妨令 \(a_i\) 叫时间,\(i\) 叫日期。
\(dis_i\) 表示到达点 \(i\) 的最早日期。对每条边记录它所属的时间。
把每个 \(i\)\(a_i\) 分组,就可以二分出在当前 \(dis_u\)\(u\to v\) 这条边可通行的最早日期。
把这个当作边权跑 dijkstra 就行了,双 \(\log\) 但是跑得挺快。

Code
const int N=2e5+5,inf=1e9;
#define pii pair
#define fi first
#define se second
int n,t,k,a[N];
map mp;
struct edge{int nxt,to,id;} e[N<<1];
int head[N],cnt=1;
il void add(int u,int v,int id) {e[++cnt]={head[u],v,id};head[u]=cnt;}
vector tim[N],nxt[N];
int dis[N];
il int get(int nowt,int id)
{
    auto ans=upper_bound(nxt[id].begin(),nxt[id].end(),nowt);
    if(ans==nxt[id].end()) return inf;
    else return *ans;
}
int main()
{
    n=read(),t=read();
    for(int i=1;i<=t;i++)
    {
        int x=read();
        for(int j=1;j<=x;j++)
        {
            int u=read(),v=read();
            add(u,v,i),add(v,u,i);
        }
    }
    k=read();
    for(int i=1;i<=k;i++) a[i]=read(),nxt[a[i]].push_back(i);
    priority_queue,greater >q;
    for(int i=1;i<=n;i++) dis[i]=inf;
    dis[1]=0,q.push(pii(0,1));
    while(!q.empty())
    {
        int u=q.top().se,f=q.top().fi; q.pop();
        if(dis[u]!=f) continue;
        for(int i=head[u];i;i=e[i].nxt)
        {
            int v=e[i].to,id=e[i].id;
            int w=get(dis[u],id);
            if(dis[v]>w) dis[v]=w,q.push(pii(dis[v],v));
        }
    }
    if(dis[n]==inf) printf("-1\n");
    else printf("%d\n",dis[n]);
    return 0;
}

C. Minimum Array

被 A2 卡没时间了 /oh

Description

给一个长度为 \(n\) 的初始序列 \(a\)\(q\) 次操作。每次操作形如给 \(a_l\sim a_r\) 的值加上 \(k\)
依次进行这些操作,求过程中得到过字典序最小的序列。
$n,q\le 5\times 105, -109\le a_i,k\le 10^9 $。

Solution 1

考虑依次进行每个操作,维护当前能使答案字典序最小的操作编号 \(ans\)

只考虑从上一个 \(ans\) 到当前时刻的操作,设它们操作后形成的序列为 \(b\)。那么当前的实际序列就是 \([1,ans]\) 的实际序列与 \(b\) 数组对应位相加后的结果。
当前序列比 \([1,ans]\) 字典序更小的充要条件是 \(b\) 中第一个不为 \(0\) 的位置的值 \(<0\),证明是显然的。每次修改后判断,满足这个条件就更新 \(ans\),并清零 \(b\) 数组。

也就是说我们只需要一个数据结构支持区间加、求第一个不为 \(0\) 的位置、单点求值。
直接上线段树是可做的。不过发现第一个不为 \(0\) 的位置只能是某个 \(l_i\)\(r_i+1\),所以把这些点塞进 set 里暴力判断即可,区间加用树状数组维护。

Solution 2

看了下官方题解。
我们直接在差分数组上考虑这个问题,修改变成了单点,更新答案还是求第一个不为 \(0\) 的位置。这样就不用写树状数组了。

Code ``` #define int long long const int N=5e5+5,inf=1e9; int t,n,a[N]; struct node{int l,r,k;} q[N]; struct BIT { int tr[N]; il void modify(int x,int k) {for(;x<=n;x+=x&(-x)) tr[x]+=k;} il void add(int l,int r,int k) {modify(l,k),modify(r+1,-k);} il int query(int x) {int res=0;for(;x;x-=x&(-x)) res+=tr[x];return res;} }tr; int ans; signed main() { int T=read(); while(T--) { n=read(); ans=0; for(int i=1;i<=n;i++) a[i]=read(),tr.tr[i]=0; t=read(); set st; for(int i=1;i<=t;i++) q[i].l=read(),q[i].r=read(),q[i].k=read(); for(int i=1;i<=t;i++) { tr.add(q[i].l,q[i].r,q[i].k); st.insert(q[i].l),st.insert(q[i].r+1); while(st.size()&&!tr.query(*st.begin())) st.erase(st.begin()); if(!st.empty()&&tr.query(*st.begin())<0) { for(int j=i;j>ans;j--) tr.add(q[j].l,q[j].r,-q[j].k); ans=i,st.clear(); } } for(int i=1;i<=n;i++) tr.tr[i]=0; for(int i=1;i<=n;i++) tr.modify(i,a[i]-a[i-1]); for(int i=1;i<=ans;i++) tr.add(q[i].l,q[i].r,q[i].k); for(int i=1;i<=n;i++) printf("%lld ",tr.query(i)); printf("\n"); } } ```

你可能感兴趣的:(算法,数据结构)