湖南多笑 9

组队出道

zzomega 左ls
K2SO4 张ls
没有ID 刘ls

4h过6题

题解

B

张ls开的,我跟着一起看看

首先有没有圆没有区别
其次考虑维护“纯粹的”相交,即没有包含关系的相交。
这样的话直接维护相交就会产生不要求的“包含”关系。
所以考虑维护不交。
按照右端点排序,在\(r_i<r_j\)的情况下,\(i,j\)不交的两种状况
\(r_i<l_j\) 或者\(l_i>l_j\)
两个树状数组维护一下就行

C

当时我考虑这是一个数论题
exgcd完了就没事了,然后通解也能构造
不过左ls发现可能会出像6 8 10 这种反例
然后左ls发现数据范围超小,可以直接背包
遂直接背包。
后面的输出数字记录状态转移即可

D

其实答案的上界是 \(O(\log^2 t)\),证明不难。
然后我们给了一个类似倍增的做法。
在走到"下一次冲刺就会冲过头"的时候,选择一种策略
要么冲过头返回,要么刹车走剩下的一段。
从中选择最优策略,其余情况直接无脑冲刺显然更优。
这个时候直接搜会超时,加个记忆化就过了
左ls直接搜寄了,然后我给加个玄学记忆化就过了
正经的贪心过程事实上会把这题提升到几乎不可做的难度

E

我干的
假设第\(i\)天合法的评分集合为\(A_i\)
那么\(\forall j>i,A_i\subseteq A_j\)
所以实际上对于第\(i+1\)天来说,前\(i\)天的选择肯定会占掉\(i\)个位置
而且事实上显然也只会占这么多。
那只需要考虑第\(i+1\)\(A_{i+1}\)的大小。
即所有满足\(a_j\geq\frac{k}{i+1},\)的数量
那么就是\(n-\lceil\frac{k}{i+1}\rceil+1\)
减去\(i\)个已经用的即可。
状态转移方程:

\[f_{i+1}=f_i\cdot(n-\lceil\frac{k}{i+1}\rceil+1-i) \]

F

这个也是我干的
注意到如果能直接停电车的话肯定直接骑电车零代价到
否则就得考虑走着走,
注意到空手去拉一个点的物资回电车一定更优,
所以也就是说本质上是求

\[\sum dis_i\cdot a_i \]

其中\(dis_i\)表示\(i\)任意一个能停电车的点的最短距离
这个问题就是经典的建虚点跑最短路问题
注意给距离取模就没了

M

大概就是按照点对距离取点更新距离
然后每确定一个点的半径就去用距离减半径更新其他点。
这里我开始考虑的是把初始的点对连带着更新的点对一起扔进堆里
每次取最小的点对出来更新
但是其实更新就直接更新就行了,没有其他的乱七八糟的。

L

我们准时下班不搞别的
所以这题四点下班的时候写了一半然后扔了跑路
不过当然思路是已经成熟的
首先很显然先把小的扔到两边然后才会动大的
由于邻项交换不改变相对顺序,所以把小的搞定之后看大的那些相对顺序其实是不会变的。
也就是等价于直接把小的删掉。
然后就是考虑次数。
无非就是往左或者往右扔,取最小的那边肯定更优。
所以只需要维护当前位置上现在是第几个数。
考虑每个位置初始都是\(1\),每删掉一个位置的数,就把这个位置变成\(0\)
这个数组的前缀和,其实就是初始在\(i\)的数删数后的位置。
考虑到有相同值的时候排序不好排,可以把相同的几个元素同时做
当然也可以从大到小做,往回插


A

被诈骗了T_T
按照常理来说这题应该是树形DP
但是这个题如果能带修带查的话,
那么单次知道答案的时间成本至少不是重新做一遍树形DP
那么换种思维考虑。
如果一个节点\(i\)和它的子节点\(j\)不同色,
那么这个节点\(j\)一定会翻
接下来整棵树和根节点\(rt\)同色,再看\(a_{rt}\)决定翻不翻
所以实质上只需要考虑有多少条边\((u,v)\)满足\(a_u\not=a_v\)
这很简单,维护子节点的个数\(d\)和子节点中和自己不同色的个数\(c\)
那么翻转节点\(i\)本质上是\(c_i\leftarrow d_i-c_i,c_{fa_i}\)视情况更新
数据结构可以单次\(\log\),不过当然直接维护答案就行,单次\(O(1)\)

1A
#include <bits/stdc++.h>
using namespace std;
const int o=222222;
int n,q,T;
struct Graph{
	struct edge{
		int t,n;
	}p[o];
	int d[o],c[o],x,h[o],cnt,a[o],f[o],rt,ans;
	void add(int s,int t){
		cnt++;
		p[cnt].t=t;
		p[cnt].n=h[s];
		h[s]=cnt;
	}
	void dfs(int x,int fa){
		for(int i=h[x];i;i=p[i].n){
			int y=p[i].t;
			if(y==fa)continue;
			d[x]+=1;
			if(a[y]!=a[x])c[x]+=1;
			f[y]=x;
			dfs(y,x);
		}
		ans+=c[x];
	}
	void flip(int x){
		if(x!=1){
			if(a[f[x]]==a[x])c[f[x]]++,ans++;
			else c[f[x]]--,ans--;
		}
		if(x==rt){
			if(a[x])ans--;
			else ans++;
		}
		ans-=c[x];
		c[x]=d[x]-c[x];
		ans+=c[x];
		a[x]^=1;
	}
	void changeroot(int x){
		ans-=a[rt];
		rt=x;
		ans+=a[rt];
	}
	int ques(){return ans;}
	void clear(){
		for(int i=1;i<=cnt;i++)p[i]=p[0];
		for(int i=1;i<=n;i++){
			h[i]=0,c[i]=0,a[i]=0,f[i]=0,d[i]=0;
		}
		rt=0,ans=0,cnt=0;
	}
}G;
int read(){
	int i=1,j=0;
	char ch=getchar();
	while(ch>'9'||ch<'0'){
		if(ch=='-')i=-1;
		ch=getchar();
	}
	while(ch>='0'&&ch<='9'){
		j=j*10+ch-48;
		ch=getchar();
	}
	return i*j;
}
void in(){
	n=read(),q=read();
	for(int i=1;i<=n;i++)G.a[i]=read();
	for(int i=1,x,y;i<n;i++){
		x=read(),y=read();
		G.add(x,y);
		G.add(y,x);
	}
}
void pre(){
	G.ans=G.a[1];
	G.rt=1;
	G.dfs(1,0);
	printf("%d\n",G.ques());
}
void work(){
	int op,x;
	for(int i=1;i<=q;i++){
		op=read(),x=read();
		if(op==1)G.flip(x);
		if(op==2)G.changeroot(x);
		printf("%d\n",G.ques());
	}
}
int main(){
	T=read();
	while(T--){
		in(),pre(),work(),G.clear();
	}
	return 0;
}

I

这个题也是一开始就开的,一直在想申必哈希
其实好像不用哈希,直接做就行
题解给了几个简单的性质
性质一:合法的子串以\(1\)开头
这个没啥好解释的,如果你开头连\(1\)都不是,那你肯定是没有办法删成一个开头是\(1\)的序列
性质二:合法的子串末尾加个\(1\)还是合法的
这个说白了就是直接上操作在末尾插\(1\)而已,很显然
性质三:如果\(a_{i+1}=a_i+1\),那么\(a_i\)\(a_{i+1}\)是同一次操作插入的
因为一次不可能直接从\(2\)开始插入,所以不可能说先插\(a_{i+1}\)再插\(a_i\)
所以只能是先插\(a_i\)再插\(a_{i+1}\)
这样的话如果\(a_i>1\)的话那这两个只能是跟别的一起插进来
否则\(a_i=1,a_{i+1}=2,\)仍然可以看作是一起插进来的,证毕
性质四:如果\([l,r]\)中间有极大合法子串\([p,q]\),则\([l,r]\)合法等价于删去\([p,q]\)后的序列合法。
这个的话就是删掉后的序列合法,那么按照构造\([p,q]\)的方法重新加回去还是合法。
删掉的部分不合法,那么\([l,r]\)肯定也不会合法。

根据上述性质,仍然考虑经典的以\(i\)结尾的合法串数量\(g_i\)
综合性质一和性质二,以\(1\)结尾的\(i\),合法串数量包括自己和前面所有合法的串\(g_{i-1}\)
即$$g_i=g_{i-1}+1,a_i=1$$
否则就要找一个同期加入的,并且中间一段合法的\(j\)\(g_i=g_j\)
这个\(j\)只需要考虑在\(i\)前最近的一个

考虑具体的做法,这个过程类似于一个括号匹配,不过还是不太显然。
维护一个二元组,第一个是下标\(pos\),第二个是\(val=a_{pos}\)
记这个二元组所组成的栈顶为\(top\).
如果\(val_{top}=a_i-1\),那么说明\(pos_{top}\)就是\(i\)所对应的满足条件的\(j\),然后再更新一下\(top\)就完了。
否则就不是,将其出栈。
如果是\(a_i=1\)的话直接新建一个节点入栈即可。

一发切
#include <bits/stdc++.h>
using namespace std;
#define int long long 
const int o=2222222;
int g[o],a[o],n,ans;
struct node{
    int pos,val;
}c;
stack<node>q;
void in(){
    cin>>n;
    for(int i=1;i<=n;i++)cin>>a[i];
}
void work(){
    for(int i=1;i<=n;i++){
        if(a[i]==1){
            if(q.empty())g[i]=1;
            else g[i]=g[q.top().pos]+1;
            c.pos=i,c.val=1;
            q.push(c);
            continue;
        }
        while(!q.empty()){
            c=q.top(),q.pop();
            if(c.val==a[i]-1){
                g[i]=g[c.pos];
                c.pos=i,c.val=a[i];
                q.push(c);
                break;
            }
        }
    }
    for(int i=1;i<=n;i++)ans+=g[i];
}
void out(){cout<<ans<<"\n";}
#undef int 
int main(){
    in(),work(),out();
    return 0;
}

H

主要是考虑两个问题,
第一个是任意长的整数拆分里面怎么去算所有的数对
第二个是去掉任意一个\(c_i\)怎么做

这个显然从序列下手就绝对平等的卡死每一个人。
注意到值域很小,能够从值域下手枚举每个数对。
那么接下来只需要考虑两个数\((i,j)\)出现的次数就好了

考虑数对\((x,y),x\not=y\) 的情况,
假设一个序列里有\(a\)\(x\)\(b\)\(b\),那么\((x,y)\)做的贡献次数是\(a\cdot b\)
恰好枚举\(i\in [1,a],j\in [1,b]\)的话就可以把这个序列的所有贡献算完并且不会重复。
所以我们在算\((x,y)\)的贡献总次数,其实就是\(\sum\limits_{ix+jy\leq n}f_{n-ix-jy}\)
这里枚举\(i,j,x,y\)即可通过,时间复杂度\(O(n^2\log^2 n)\)

考虑更进一步的优化就是把\(\sum\limits_{ix+jy\leq n}f_{n-ix-jy}\)对每个\(y\)\(s=n-ix\)进行预处理
这一部分的处理直接暴力枚举\(y\)\(s\),直接对所有合法的\(j\)进行累加。
之后的话就可以只枚举\(s\)
两部分时间复杂度都是\(O(n^2\log n)\)

最后考虑 \(x=y\) 的情况
这一部分只需要枚举\(x\)\(i\)
仍然考虑一个序列贡献次数对\(i\)拆分。
如果一个\(x\)出现了\(a\)次的话,总贡献次数是\(\frac{a(a-1)}{2}\)
这样的话做拆分,每个\(i\)\((i-1)\)
把这一部分连带剩余的方案数\(f_{n-ix}\)一起乘起来就是总贡献数
所以每个\(x\)的总贡献是\(f_{n-ix}\cdot x(i-1)\)

AI写的码一遍过了,我的码WA了十几发

点击查看代码
#include <bits/stdc++.h>
using namespace std;
#define int long long 
const int o=2222,mod=1e9+7;
int n,m,c[o],f[o],g[o][o],ans;
//g[j][i]:y对应j,n-s对应i时需要的f之和
void in(){
    cin>>n>>m;
    for(int i=1,x;i<=m;i++){
        cin>>x;
        c[x]=1;
    }
}
void pre(){
    f[0]=1;
    for(int i=1;i<=n;i++){
        if(c[i])continue;
        for(int j=i;j<=n;j++)f[j]=(f[j]+f[j-i])%mod;
    }
    /*for(int i=1;i<=n;i++)cout<<f[i]<<" ";
    puts("");*/
    for(int i=1;i<=n;i++){
        for(int j=1;j<=n;j++){
            //if(c[j])continue;
            for(int k=1;k<=i/j;k++)g[j][i]=(g[j][i]+f[i-k*j])%mod;
        }
    }
    /*for(int j=1;j<=n;j++){
        for(int i=1;i<=n;i++)cout<<g[j][i]<<" ";
        cout<<"\n";
    }*/
}
void work(){
    for(int x=1;x<=n;x++){
        for(int y=x+1;y<=n;y++){
            if(c[x]||c[y])continue;
            int now=__gcd(x,y);
            for(int i=1;i*x<=n;i++)ans=(ans+now*g[y][n-i*x]%mod)%mod;
        }
    }
    for(int x=1;x<=n;x++){
        if(c[x])continue;
        for(int i=1;i<=n/x;i++)ans=(ans+x*(i-1)%mod*f[n-i*x]%mod)%mod;
            //for(int j=1;j<=i;j++)ans=(ans+x*(j-1)%mod*f[n-j*x]%mod)%mod;
    }
}
void out(){cout<<ans<<"\n";}
#undef int 
int main(){
    in();pre();work();out();
    return 0;
}
posted @ 2025-05-26 20:57  2K22  阅读(4)  评论(0)    收藏  举报