Loading

正睿 11.12 做题笔记

T1

一个经典讨论,如果把乘除换成加减一样可以做,赛场上没有见过,但实际上之前正睿出过这个题,自己却没有补,这说明自己不能懒,要把时间放在调题和打代码上,或者是文化课上。

对一个数进行操作,等价于对前缀积进行交换,所以我们直接判断即可。

代码:

#include<bits/stdc++.h>
#define N 200010
using namespace std;

template<typename T> inline void read(T &x){
	x=0;int f=1;char c=getchar();
	for(;!isdigit(c);c=getchar()) if(c=='-') f*=-1;
	for(;isdigit(c);c=getchar()) x=x*10+c-'0';
	x*=f;
}

struct Node{
	int a[7];
	inline Node(){memset(a,0,sizeof(a));}
	inline Node operator * (const Node &b)const{
		Node c;
		for(int i=1;i<=6;i++) c.a[i]=a[i]+b.a[i];
		return c;
	}
	inline bool operator == (const Node &b)const{
		for(int i=1;i<=6;i++) if(a[i]!=b.a[i]) return 0;return 1;
	}
	inline bool operator < (const Node &b)const{
		for(int i=1;i<=6;i++) if(a[i]!=b.a[i]) return a[i]<b.a[i];
		return 0;
	}
	inline bool operator != (const Node &b)const{
		return !((*this)==b);
	}
}b[N],Sum1[N],c[N],Sum2[N];

multiset<Node> S1,S2;
int n;

inline void Init(){
	read(n);
	for(int i=1;i<=n;i++){
		for(int j=1;j<=6;j++) read(b[i].a[j]);
		for(int j=1;j<=6;j++){
			int x;read(x);b[i].a[j]-=x;
		}
		Sum1[i]=Sum1[i-1]*b[i];
	}
	for(int i=1;i<=n;i++){
		for(int j=1;j<=6;j++) read(c[i].a[j]);
		for(int j=1;j<=6;j++){
			int x;read(x);c[i].a[j]-=x;
		}
		Sum2[i]=Sum2[i-1]*c[i];
	}
}

inline void Solve(){
	if(Sum1[n]!=Sum2[n]) return(void)puts("No");
	for(int i=1;i<=n;i++) S1.insert(Sum1[i]);
	for(int j=1;j<=n;j++) S2.insert(Sum2[j]);
	puts(S1==S2?"Yes":"No");
}

int main(){
	Init();Solve();return 0;
}

T2

这个题目是根号分治,是我过的第一道根号分治的题目,其实如果想到根号分治的话还是比较好做的,启示我在做题的时候要扩宽思路,同时要注意数据范围,这个题的切入点在所以询问和是 \(1e5\) 级别的。

考虑根号分治,对于一个询问,如果总点数大于等于 \(\sqrt n\) ,我们考虑直接上 \(O(n)\) 的 dp,这样复杂度是对的,因为这样的询问不会超过 \(\sqrt n\) 个。否则,总点数就会小于 \(\sqrt n\),考虑怎么做。

如果我们能预处理出来每个数距离其最远的前 \(\sqrt n\) 个点,那么我们就可以做这个题。预处理的时候双指针归并就可以,注意去重,还是要开一个 vis。

下面代码中,因为 \(Merge\) 常数过大,所以我们分治的参数调成 \(100\) 可以过。

代码:

#include<bits/stdc++.h>
#define dd double
#define ld long double
#define ll long long
#define uint unsigned int
#define ull unsigned long long
#define N 100010
#define M 200010
using namespace std;

const int INF=0x3f3f3f3f;

template<typename T> inline void read(T &x) {
    x=0; int f=1;
    char c=getchar();
    for(;!isdigit(c);c=getchar()) if(c == '-') f=-f;
    for(;isdigit(c);c=getchar()) x=x*10+c-'0';
    x*=f;
}

template<typename T> inline T Max(T a,T b){return a<b?b:a;}
template<typename T> inline T Min(T a,T b){return a<b?a:b;}

struct edge{
    int to,next;
    inline void Init(int to_,int ne_){
        to=to_;next=ne_;
    }
}li[M];
int head[N],tail;

inline void Add(int from,int to){
    li[++tail].Init(to,head[from]);
    head[from]=tail;
}

struct Node{
    int val,id;
    inline Node(){}
    inline Node(int val,int id) : val(val),id(id) {}
};
vector<Node> P[N];

int n,m,Q,Len;

inline void Init(){
    read(n);read(m);read(Q);
    for(int i=1;i<=m;i++){
        int from,to;read(from);read(to);
        Add(to,from);
    }
    Len=100;
//    printf("%d\n",ques[Q].T);
//    printf("Len=%d\n",Len);

}

bool Vis[N];

inline vector<Node> Merge(int a,int b){
//    printf("a=%d b=%d\n",a,b);
    vector<Node> res;vector<Node>().swap(res);
    int ia=0,ib=0,cnt=0;
//    printf("ia=%d ib=%d\n",ia,ib);
    while(cnt<Len&&(ia!=(int)P[a].size()||ib!=(int)P[b].size())){
        Node nowa(-INF,-INF),nowb(-INF,-INF);
        if(ia!=(int)P[a].size()){nowa=P[a][ia];nowa.val++;}if(ib!=(int)P[b].size()){nowb=P[b][ib];}
//        printf("nowa.id=%d nowa.val=%d nowb.id=%d nowb.val=%d\n",nowa.id,nowa.val,nowb.id,nowb.val);
        if(ia!=(int)P[a].size()&&Vis[nowa.id]){ia++;continue;}if(ib!=(int)P[b].size()&&Vis[nowb.id]){ib++;continue;}
        if(nowa.val<nowb.val){res.push_back(nowb);ib++;Vis[nowb.id]=1;}
        else{res.push_back(nowa);ia++;Vis[nowa.id]=1;}cnt++;
    }
    for(Node x:res) Vis[x.id]=0;
	return res;
}

inline void Print(int i){
	printf("i=%d:\n",i);for(Node x:P[i]) printf("id=%d val=%d\n",x.id,x.val);puts("");
}

inline void PreWork(){
    for(int i=1;i<=n;i++){
        for(int x=head[i];x;x=li[x].next){
            int to=li[x].to;P[i]=Merge(to,i);
        }
        int now=P[i].size();
        if(now<Len){P[i].push_back(Node(0,i));}
    }
//    for(int i=1;i<=n;i++){
//        printf("i=%d:\n",i);for(Node x:P[i]) printf("id=%d val=%d\n",x.id,x.val);puts("");
//    }
//	Print(17861);
}

int f[N],T;
vector<int> Y;

inline void Dp(){
    fill(f+1,f+n+1,-1);int k=T;f[k]=0;
    for(int x:Y) Vis[x]=1;
    for(int i=k;i>=1;i--){
        if(f[i]==-1) continue;
        for(int x=head[i];x;x=li[x].next){
            int to=li[x].to;f[to]=Max(f[to],f[i]+1);
        }
    }
    int Ans=-1;for(int i=1;i<=n;i++) if(!Vis[i]) Ans=Max(Ans,f[i]);
    for(int x:Y) Vis[x]=0;
    printf("%d\n",Ans);
}

inline void GetAns(){
    int k=T;
    for(int x:Y) Vis[x]=1;
    int Ans=-1;
    for(int i=0;i<(int)P[k].size();i++) if(!Vis[P[k][i].id]){Ans=P[k][i].val;break;}
    for(int x:Y) Vis[x]=0;printf("%d\n",Ans);
}

inline void Solve(){
    for(int i=1;i<=Q;i++){
    	read(T);vector<int>().swap(Y);int y;read(y);
		for(int i=1;i<=y;i++){int x;read(x);Y.push_back(x);}
        if((int)Y.size()>=Len){/*printf("Enter 1\n");*/Dp();}
        else{/*printf("Enter 2\n");*/GetAns();}
    }
}

int main(){
//    freopen("my.in","r",stdin);
//    freopen("my.out","w",stdout);
    Init();PreWork();Solve();return 0;
}

T3

一道比较好的题,这道题体现出我的思维不足。

首先很自然的想法是,如果我们能够扩展出每个点向左向右最多能走到哪里,这个题就做完了,我们考虑怎么做这个东西。

\(Nxt_i\) 表示能开 \(i\) 右边门的那个钥匙最靠左出现在哪里。设 \(Pre_{i,j}\) 表示门的编号在 \([i,i+2^j-1]\) 之间的门,要是编号最小时多少。

很明显,上面这两个东西我们很容易就可以预处理出来,我们考虑如何计算我们上面的东西。

\(l_i,r_i\) 分别表示 \(i\) 向左向右扩展到哪里,假设已经处理出来了 \(l_{i-1},r_{i-1}\),对于当前的 \(r_i\),如果我们可以在 \(r_i\) 的左边拿到开 \(l_i\) 左边门的钥匙,我们就可以有:\(l_i:=l_{l_i-1}\)。然后右边如何扩展呢,我们倍增来扩展,枚举一个数,然后看这个区间内的钥匙有没有被拿走。

注意到更新 \(l\) 是均摊 \(O(n)\) 的,所以循环最多 \(O(n)\) 次,所以后者是 \(O(n\log n)\) 的。

代码:

#include<bits/stdc++.h>
#define dd double
#define ld long double
#define ll long long
#define uint unsigned int
#define ull unsigned long long
#define N 100010
#define M 200010
using namespace std;

const int INF=0x3f3f3f3f;

template<typename T> inline void read(T &x) {
    x=0; int f=1;
    char c=getchar();
    for(;!isdigit(c);c=getchar()) if(c == '-') f=-f;
    for(;isdigit(c);c=getchar()) x=x*10+c-'0';
    x*=f;
}

template<typename T> inline T Max(T a,T b){return a<b?b:a;}
template<typename T> inline T Min(T a,T b){return a<b?a:b;}

struct edge{
    int to,next;
    inline void Init(int to_,int ne_){
        to=to_;next=ne_;
    }
}li[M];
int head[N],tail;

inline void Add(int from,int to){
    li[++tail].Init(to,head[from]);
    head[from]=tail;
}

struct Node{
    int val,id;
    inline Node(){}
    inline Node(int val,int id) : val(val),id(id) {}
};
vector<Node> P[N];

int n,m,Q,Len;

inline void Init(){
    read(n);read(m);read(Q);
    for(int i=1;i<=m;i++){
        int from,to;read(from);read(to);
        Add(to,from);
    }
    Len=100;
//    printf("%d\n",ques[Q].T);
//    printf("Len=%d\n",Len);

}

bool Vis[N];

inline vector<Node> Merge(int a,int b){
//    printf("a=%d b=%d\n",a,b);
    vector<Node> res;vector<Node>().swap(res);
    int ia=0,ib=0,cnt=0;
//    printf("ia=%d ib=%d\n",ia,ib);
    while(cnt<Len&&(ia!=(int)P[a].size()||ib!=(int)P[b].size())){
        Node nowa(-INF,-INF),nowb(-INF,-INF);
        if(ia!=(int)P[a].size()){nowa=P[a][ia];nowa.val++;}if(ib!=(int)P[b].size()){nowb=P[b][ib];}
//        printf("nowa.id=%d nowa.val=%d nowb.id=%d nowb.val=%d\n",nowa.id,nowa.val,nowb.id,nowb.val);
        if(ia!=(int)P[a].size()&&Vis[nowa.id]){ia++;continue;}if(ib!=(int)P[b].size()&&Vis[nowb.id]){ib++;continue;}
        if(nowa.val<nowb.val){res.push_back(nowb);ib++;Vis[nowb.id]=1;}
        else{res.push_back(nowa);ia++;Vis[nowa.id]=1;}cnt++;
    }
    for(Node x:res) Vis[x.id]=0;
	return res;
}

inline void Print(int i){
	printf("i=%d:\n",i);for(Node x:P[i]) printf("id=%d val=%d\n",x.id,x.val);puts("");
}

inline void PreWork(){
    for(int i=1;i<=n;i++){
        for(int x=head[i];x;x=li[x].next){
            int to=li[x].to;P[i]=Merge(to,i);
        }
        int now=P[i].size();
        if(now<Len){P[i].push_back(Node(0,i));}
    }
//    for(int i=1;i<=n;i++){
//        printf("i=%d:\n",i);for(Node x:P[i]) printf("id=%d val=%d\n",x.id,x.val);puts("");
//    }
//	Print(17861);
}

int f[N],T;
vector<int> Y;

inline void Dp(){
    fill(f+1,f+n+1,-1);int k=T;f[k]=0;
    for(int x:Y) Vis[x]=1;
    for(int i=k;i>=1;i--){
        if(f[i]==-1) continue;
        for(int x=head[i];x;x=li[x].next){
            int to=li[x].to;f[to]=Max(f[to],f[i]+1);
        }
    }
    int Ans=-1;for(int i=1;i<=n;i++) if(!Vis[i]) Ans=Max(Ans,f[i]);
    for(int x:Y) Vis[x]=0;
    printf("%d\n",Ans);
}

inline void GetAns(){
    int k=T;
    for(int x:Y) Vis[x]=1;
    int Ans=-1;
    for(int i=0;i<(int)P[k].size();i++) if(!Vis[P[k][i].id]){Ans=P[k][i].val;break;}
    for(int x:Y) Vis[x]=0;printf("%d\n",Ans);
}

inline void Solve(){
    for(int i=1;i<=Q;i++){
    	read(T);vector<int>().swap(Y);int y;read(y);
		for(int i=1;i<=y;i++){int x;read(x);Y.push_back(x);}
        if((int)Y.size()>=Len){/*printf("Enter 1\n");*/Dp();}
        else{/*printf("Enter 2\n");*/GetAns();}
    }
}

int main(){
//    freopen("my.in","r",stdin);
//    freopen("my.out","w",stdout);
    Init();PreWork();Solve();return 0;
}

T4

这道题更是重量级,需要若干个性质才能把这个题做出来,需要有一定的分析能力。不是一般性,我们首先令所有的区间 \(l<r\),而替代输入中原有的 \(r<l\)。考虑区间覆盖,一个翻转操作其实是令 \([l,r]\) 覆盖次数 \(-1\)\([1,l),(r,n]\) 的区间覆盖次数加一,而我们要做的,就是要最小化最大的区间覆盖次数。

一下设 \(a_i\) 表示没有进行任何翻转操作之前 \(i\) 的覆盖次数。

  • 性质一:一定存在一个最优方案,使得满足所有被翻转的区间,翻转前一定是两两相交的。

这个性质比较显然,如果两个区间不交,如果翻转这两个区间,很明显答案会比不翻转前要优,因为原先区间在的地方覆盖次数没变,而其余地方全部增加 \(2\)。就像这样:

其中红色是之前的区间,蓝色是反转之后的区间。

以下设 \(b_i\) 是按照最优方案反转之后 \(i\) 的覆盖次数。由性质一可以知道,所有需要翻转的区间的交必定不为空,设 \(t\) 为这段区间中最大的 \(b\) 所在的位置,显然最终答案应该是 \(\max_{1\le i\le n} b_i\)。设交区间为 \([x,y]\)

  • 性质二:\(b_t\ge \max_{1\le i\le n}b_i-1\),形式化的讲,\(b_t\) 要么是答案,要么是答案减一。

考虑反证法,假设 \(b_t\le \max_{1\le i\le n}b_i-2\),因为 \(t\) 一定在交区间里,所以我们一定可以找到一个右端点为 \(r\) 的区间,一个左端点为 \(l\) 的区间,那么这两个区间是需要翻转的,翻转后的区间实际上长这样:

以上图片红色为交区间,蓝色为以上所述的两个合法区间,绿色为翻转之后的区间。

如果我们不翻转这两个区间,那么容易发现 \(b_t\) 会增加 \(2\),而其它部分要么不变要么减少 \(2\),由假设可知,这样不会使答案更劣。如果仍然满足假设,我们可以重复这个操作。由此可知结论成立。

  • 性质三:对于所有的 \(a_k=\max_{1\le i\le n}a_i\),我们都有 \(k\in[x,y]\)

考虑反证法,假设存在一个下标 \(i\) 满足 \(a_i\ge a_k,k\in[x,y]\),所以我们就可以得到 \(a_i\ge a_t\)

因为 \(a_i\) 不在区间内,所以至少存在一个被翻转的区间,翻转前不包含 \(a_i\)。设一共有 \(m\) 个区间被翻转,我们可以得到式子:

\[a_i-b_i\le (m-1)-1 \]

这个式子是什么意思呢,发现 \(a_i-b_i\) 就是以前覆盖,现在不覆盖的区间个数,因为至少有一个区间不包含 \(a_i\),所以最多有 \(m-1\) 个区间包含 \(a_i\),如果把这些区间都翻转,才能最大化 \(a_i-b_i\),同时也因为至少有一个区间不包含 \(a_i\),所以翻转这个区间会让 \(b_i\) 增加 \(1\),故我们可以知道不等式成立。

由于 \(t\) 在交区间中,所以我们一定有 \(a_t-b_t=m\),我们把这个东西代入上面那个式子,然后再移项,可以得到:

\[a_i-a_t\le b_i-b_t-2 \]

因为我们有 \(a_i\ge a_t \Rightarrow a_i-a_t\ge 0\),而由性质一可以的得到有 \(b_t\ge b_i-1\Rightarrow b_i-b_t\le 1\),所以上面那个不等式不会成立,推出矛盾。

现在我们考虑如何做这个题目,我们考虑二分答案 \(x\),然后考虑如何 check 我们的答案。首先,我们可以找到 \(a_p=\max_{1\le i\le n}a_i\),然后我们根据以上性质,可以知道 \(p\) 一定在交区间中,然后因为交区间中都有 \(a_p-b_p=m\),其中 \(m\) 是被翻转的区间个数,所以 \(b_p\) 一定也是交区间中 \(b\) 最大的值,形式化的说,就是 \(b_p=\max_{x\le k\le y}b_k\),所以根据性质一,如果答案是 \(w\) 的话,我们有 \(b_p=w\)\(b_p=w+1\)。设 \(m\) 为被翻转区间的数量,所以 \(b_p=a_p-m\),所以我们可以得到 \(m=a_p-w\) 或者 \(m=a_p-w+1\),如果我们能够判断我们翻转 \(m\) 个区间,是否能够使所有位置的值都小于等于 \(w\),这个题其实就做完了。关于是否具有可二分性我们一会在证明。

然后我们考虑这个东西怎么实现,对于一个点 \(i\),考虑这个点什么时候合法,我们假设一共有 \(v\) 个区间,这些区间覆盖 \(p\)\(i\),并且属于那 \(m\) 个区间,那么我们实际上是有这样的不等式:

\[a_i-v+m-v\le w \]

上面这个式子就是说翻转那 \(v\) 个区间会使覆盖次数减少,而 \(m\) 个中其他被翻转的区间,会使答案增加,我们进行移项可以得到 \(v\) 的下界:

\[v\ge\left\lceil \frac{a_i+m-v}{2} \right\rceil \]

然后我们贪心的去考虑这个问题,对于所有覆盖 \(i\)\(p\) 的区间,我们右端点尽量更靠右的,我们把当前所有满足条件的区间都加入一个堆,至于选右端点更靠右的原因是你保证 \(p\) 左边的覆盖次数小于等于 \(w\),只需要关注被翻转的区间,但是这样并不会关照到 \(p\) 右边的覆盖次数,所以为了让 \(p\) 右边的覆盖次数尽可能小,我们一是要让翻转区间的个数尽可能的小,而是要让翻转的右端点尽可能往右。

我们贪心的翻转之后,看 \(p\) 右边覆盖次数是否满足条件即可。

代码:

#include<bits/stdc++.h>
#define dd double
#define ld long double
#define int long long
#define ll long long
#define uint unsigned int
#define ull unsigned long long
#define N 200010
#define M number
using namespace std;

const int INF=0x3f3f3f3f;

template<typename T> inline void read(T &x) {
    x=0; int f=1;
    char c=getchar();
    for(;!isdigit(c);c=getchar()) if(c == '-') f=-f;
    for(;isdigit(c);c=getchar()) x=x*10+c-'0';
    x*=f;
}

template<typename T> inline T Max(T a,T b){return a<b?b:a;}

struct squ{
    int l,r;ll s;
    inline squ(){}
    inline squ(int l,int r,ll s) : l(l),r(r),s(s) {}
    inline bool operator < (const squ &b)const{return r<b.r;}
}t;

int a[N],b[N],c[N],n,m,l,r,posi,ans,mid,Limit;
ll A[N],B[N],Cov,cha;
typedef pair<int,int> P;
vector<P> S[N];

inline void Init(){
    read(n);read(m);
    if(!m){return(void)putchar('0');}
    for(int i=1;i<=m;i++){
        read(a[i]);read(b[i]);read(c[i]);
        if(a[i]>b[i]) swap(a[i],b[i]);
        S[a[i]].push_back(make_pair(b[i],c[i]));
        A[a[i]]+=c[i];A[b[i]]-=c[i];
    }
    l=1;
    for(int i=1;i<=n;i++){
        A[i]+=A[i-1];if(A[i]>r){r=A[i];posi=i;}
    }ans=r;
    // printf("l=%d r=%d\n",l,r);
    // printf("posi=%d\n",posi);
}

priority_queue<squ> q;

inline bool Check(ll mid,ll Cnt){
    for(int i=1;i<=n;i++) B[i]=0;
    for(int i=1;i<=m;i++){B[a[i]]+=c[i];B[b[i]]-=c[i];}
    Cov=0;while(q.size()) q.pop();
    for(int i=1;i<=posi;i++){
        for(int j=0;j<S[i].size();j++){
            if(S[i][j].first>=posi) q.push(squ(i,S[i][j].first,S[i][j].second));
        }
        Limit=(i==posi?Cnt:Max(0ll,(A[i]+Cnt-mid+1)>>1));
        while(q.size()&&Limit>Cov){
            t=q.top();q.pop();
            if(Cov+t.s>Limit){q.push(squ(t.l,t.r,t.s-Limit+Cov));cha=Limit-Cov;}
            else cha=t.s;
            B[1]+=cha;B[t.l]-=(cha<<1);B[t.r]+=(cha<<1);Cov+=cha;
        }
        if(Cov<Limit) return 0;
    }
    for(int i=1;i<=n;i++){
        B[i]+=B[i-1];if(B[i]>mid) return 0;
    }
    return 1;
}

inline void Solve(){
    while(l<=r){
        // printf("l=%d r=%d\n",l,r);
        mid=(l+r)>>1;if(Check(mid,A[posi]-mid)||Check(mid,A[posi]-mid+1)){ans=mid;r=mid-1;}else l=mid+1;
    }
    printf("%lld\n",ans);
}

signed main(){
    // freopen("my.in","r",stdin);
    // freopen("my.out","w",stdout);
    Init();Solve();return 0;
}
posted @ 2021-11-12 22:08  hyl天梦  阅读(55)  评论(0编辑  收藏  举报