3月div场切片记录

CF1007 div2 F数据结构 *2700

\[\begin{flalign*} &题意:给定长度为n的序列a,最多删除k个数字,设剩余序列为b,长为m&\\& 若存在下标i\in[1,m]使得任取j\in[1,m]都有b_j>=p-|j-i|,则满足条件,求最大p\\& 思路:二分答案,思考如何check,下面称i处为塔顶\\& 枚举塔顶,看左(右)侧最少需要删除多少数字,对称问题所以只考虑左侧\\& 如果现在堆顶在j,若i<j能被选取,则a_i\ge p-cnt\\& cnt是(i,j]中未被删除的数的数量,cnt也是这个数右侧选择的数的数量\\& 则p-a_i-cnt\le 0时即可选取\\& 直观考虑到可选数集合大小必定是单调不降的,上一个数选的数,这个数必定能选(aa)\\& 比如序列:p-3,p-4,p-2,p-1,p,p\\& 第一个p的时候可以选的数字有3个,第二个p的时候可以选5个\\& 选好一个数后,将[1,i)区间减少1,代表这个区间的右侧选择的数多了一个\\& 同时将i点改成不能选的状态,由于是离散的\\& 因而可以线段树上二分求最左的<=0的点\\& 这个做法根本上是在维护一个单增的答案数量,通过线段树优美性质维护\\& 因为有区间加所以常数略大\\& 尝试使用树状数组维护差分做,但树状数组上倍增必须是在一个单调序列上做\\& 所以不能照搬上面线段树的做法\\& 树状数组得维护一个单调的序列\\& 如果维护序列序列数值的差分的话,不能得到一个单调的序列\\& 比如选了一个数要赋值成一个>0的值,会导致不单调\\& 而且树状数组倍增只能找一个单增序列中<=k的最右的x\\& 要找一个最左的可以选的未被选的点,需要全体取反倒序遍历\\& 根据上面aa的发现答案序列是单调的,所以维护答案的差分\\& 所以考虑一个数b_j,能作为塔的底部被选择,则b_j\ge p-右侧被选数\\& 考虑右侧所有塔顶,左侧所有塔顶必定能对它们的答案做贡献\\& 那么加入一个塔顶(\ge p的数),所有右侧塔顶的答案必定加1\\& 所以我们倒序维护塔顶左侧能选的数字的差分,前缀和i得到第i个塔顶当前的答案\\& 再考虑加入非塔顶j的影响,首先这个数右侧必定要存在塔顶\\& 其次这个数若对从右往左数第i个塔顶有贡献,那么必定对第i-1个塔顶做贡献,见上文aa处\\& 所以找最左的塔顶i使得:b_j-p+塔顶i左侧已选数\le0\\& 就是求最大i使得1-i前缀和\ge p-b_j-1(减1因为我算的时候塔顶自己没包含)\\& 因为上面已经全体取反,变成求最大i使得1-i前缀和\le b_j-p+1\\& 因为要跑一个最大的小于等于还需要限制不选到当前更左侧\\& 所以限制上面两个式子全都>0,两边+=n+p即可\\& 一直保持塔顶的值为n+p-左侧选择数的数量\\& 再对1-i号塔顶区间减1\\& 最后倒着跑一遍前缀和就能求完每个点左侧最多保留数的数量了 \end{flalign*} \]

CF1008 div2 C构造

\[\begin{flalign*} &题意:给定2n(n=2e5)个不同数\in [1,1e9],构造出一个未出现数字x&\\& x \in [1,1e18]使得x+其中n个数之和=剩下n个数之和\\& 思路:考虑到未出现数字,可以往最值去构造,比如构造出一个x比max还要大\\& 我选择构造最大值,先排序\\& 取出最小的n-1个数+x=剩下n+1个数,必定是比原先最大值要大的数 \end{flalign*} \]

CF1008 D dp *1700

\[\begin{flalign*} &题意:一个关卡由n对门组成。每对门包含一个左门和一个右门&\\& 每个门执行两种操作中的一种:\\& (+\ a):将车道上的人数增加一个常数 \\& (x\ a):将车道上的当前人数乘以一个整数 \\& 每次操作增加的人数可以分配到任一车道\\& 但是,已在一条车道上的人数不能被移动到另一条车道上。\\& 最初,每个车道上都有一个人。您的任务是确定在关卡结束时可以达到的最大总人数。&\\& 思路:a为一直走左侧的数量,b为一直走右侧,add为可以任意分配的数量 \end{flalign*} \]

void solve(){
	cin>>n;
	int a=1,b=1,x,y;
	int add=0;
	char xx,yy;
	for(int i=1;i<=n;i++){
		cin>>xx>>x>>yy>>y;
		if(xx=='+'&&yy=='+'){
			add+=x+y;
		}
		else if(xx=='+'&&yy=='x'){
			b+=add;
			add=b*(y-1);
			add+=x;
		}
		else if(xx=='x'&&yy=='+'){
			a+=add;
			add=a*(x-1);
			add+=y;
		}
		else{
			int old=add;
			if(y>x){
				add=(x-1)*a+(y-1)*(b+add);
				b+=old;
			}
			else if(x==y){//注意 相等可以等之后再分配
				add+=(x-1)*a+(y-1)*(b+add);
			}
			else{
				add=(x-1)*(a+add)+(y-1)*b;
				a+=old;
			}
		}
	}
	cout<<add+a+b<<endl;
}

CF1493D DS *1800

题意:n=v=2e5,q次询问,每次单点乘x,全局查gcd,考虑到直接维护会爆long long

因此对每个质数开一个multiset,且每个数质因子个数是<log(v)级别的,因此复杂度2log

void sieve()
{
	for (int i = 2; i < N; i++) isprime[i] = 1;
	for (int i = 2; i < N; i++){
		if (isprime[i]) prime.push_back(i);
		for (int &it : prime){
			if (1ll * i * it >= N) break;
			isprime[i * it] = 0;
			if (i % it == 0) break;
		}
	}
    for (int &i : prime)
        for(int j=1;j<=N/i;j++)
            v[i*j].push_back(i);
}
multiset<int>st[N];
map<int,int>mp;//序列中多少位置出现该质因子
int ans=1;
void solve(){
    int q;
    cin>>n>>q;
    for(int i=1;i<=n;i++) {
        cin>>a[i];
        int c=a[i];
        for(auto j:v[a[i]]){
            while(c%j==0) {
                c/=j;
                if(!st[j].count(i)) mp[j]++;
                st[j].insert(i);
                if(mp[j]==n){
                    mp[j]=0;
                    for(int i=1;i<=n;i++){
                        st[j].erase(st[j].find(i));
                        if(st[j].find(i)!=st[j].end()) mp[j]++;
                    }
                    ans*=j;
                    ans%=mod;
                }
            }
        }
    }
    while(q--){
        int p,x;
        cin>>p>>x;
        int y=x;
        for(auto j:v[x]){
            while(y%j==0) {
                if(!st[j].count(p)) mp[j]++;
                y/=j,st[j].insert(p);
                if(mp[j]==n){
                    mp[j]=0;
                    for(int i=1;i<=n;i++){
                        st[j].erase(st[j].find(i));
                        if(st[j].find(i)!=st[j].end()) mp[j]++;
                    }
                    ans*=j;
                    ans%=mod;
                }
            }
        }
        cout<<ans<<endl;
    }
}

CF1935C 维护最值 *1700

给你n个权值a和b,求最大个数使得$\sum a+max_b-min_b\leq L $$,\ n^2 \leq 4e6$

考虑一个假的反悔贪心,按照b升序排列,在[l,r]中找到最大的sum使得\(sum+b_r-b_l\leq L\)

但这样最后并不一定是减去\(b_l\),可能减去的是一个比\(b_l\)更大的数字,

那么枚举所有区间即可消除影响

CF1919D 思维*2100

定义一种完全二叉树满足任意非叶节点都有2个儿子,且到两个儿子的距离分别为0和1,

给你一个按照dfs序排列叶节点的深度序列,问你是否存在一个满足条件的树

两种思路:

1.逆向考虑,删除节点返回父亲,任取连续两点\(a_i和a_{i+1},|a_i-a_{i+1}|=1\),删除2点

加入\(min(a_i,a_{i+1})\),看最后是否能得到0

因为我们不知道哪些节点是同父亲的两个叶子之一,因此从最大值开始减少,最值点存在相邻元素差为1,那么这个数可以被删除,

实现形式:int->vector,BFS(删完一个数字后相邻数字入队,若该层值未被都访问过那么就不行),两个数组模拟链表

或者如下:

vector<list<int>::iterator> G[200005];
void Solve(){
    list<int> a;
    cin >> n;
    for(int i = 0;i < n;i ++)
        G[i].clear();
    for(int i = 1,x;i <= n;i ++){
        cin >> x;
        a.push_back(x);
        G[x].push_back(-- a.end());
    }
    for(int i = n - 1;i;i --){
        if(!G[i].size()) continue;
        for(auto &it:G[i]){
            if(it != a.begin() && *prev(it) == i-1)
                a.erase(it),it = a.end();
            else if(it != --a.end() && *next(it) == i - 1)
                a.erase(it),it = a.end();//it = a.end() 是让 it 不会被访问到
            else if(it == -- a.end() || *next(it) != i){
                cout << "no\n";
                return ;
            }
        }
        for(auto &it:G[i])
            if(it != a.end()) //极长连续段中的元素
                a.erase(it);
    }
    if(a.size() > 1 || a.back() != 0)
        cout << "no\n";
    else cout << "yes\n";
    
}

2.正向:问题等价于初始序列为0,任取序列中的一个x,将x+1放在x左侧或者右侧(等价于每个x+1左侧或者右侧存在一个x),能否构造得到题目给出序列,然后用单调栈判断,任取一个非0数y,左侧或者右侧必须有小于它的数存在,且等于y-1,每个数都有则满足

上面的做法暂时无法严格证明,但形象感觉上是很对的()

CF2070E 博弈+DS维护 *2200

令c0为区间0个数,c1为1的个数,考虑c0-3*c1胜负的规律

由此统计区间和>=2或者等于-1 树状数组维护即可

posted @ 2025-04-08 13:06  肆惠  阅读(21)  评论(0)    收藏  举报