24.9.8(字符串双哈希)

星期一:

补24牛客多校 三 J 倍增                                 牛客传送门

思路:因为是一个循环的字符串,想完全摊开长度可以达到4e10,无法用线段树处理,遂倍增

f【i】【j】表示从 i开始,进行 2^j局中赢了多少局,g【i】【j】表示从 i开始,2^j局后下局从哪开

先处理出 f【i】【0】和 g【i】【0】,可以双指针或二分,得到后即可倍增处理

然后对每个位置寻找答案,j从大到小搜,在总局数不超过 2*b的情况下,判断出谁先赢 b局

这里将 t扩展为>=4*a的长度,不知为何错了         ps:其实错的挺明显,长度不够

代码如下:

const int N=2e6+10,M=210;
const int INF=0x3f3f3f3f;
const int mod=1e9+7;
ll n;
int sum[N];
int f[N][20],g[N][20];
int p2[20];
void solve(){
	p2[0]=1;
	for(int i=1;i<20;i++) p2[i]=p2[i-1]*2;
	int a,b; cin >> n >> a >> b;
	string s,t; cin >> s;
	while((int)t.size()<4e5) t.append(s);
	sum[0]=t[0]-'0';
	int tsz=t.size();
	for(int i=1;i<tsz;i++){
		sum[i]=sum[i-1];
		if(t[i]=='1') sum[i]++;
	}
	for(int i=0;i<n;i++){
		int l=i,r=tsz-1,res=0;
		int su=0;
		while(l<=r){                           //二分找第一局结束的地方
			int mid=l+r>>1; su=0;
			if(i) su=sum[i-1];
			int cnt1=sum[mid]-su;
			if(cnt1>=a || mid-i+1-cnt1>=a) res=mid,r=mid-1;
			else l=mid+1;
		}
		if(!i) su=0; else su=sum[i-1];
		if(sum[res]-su>=a) f[i][0]=1;
		else f[i][0]=0;
		g[i][0]=(res+1)%n;       //res记得加一
	}
	for(int j=1;j<20;j++){
		for(int i=0;i<n;i++){
			f[i][j]=f[i][j-1]+f[g[i][j-1]][j-1];
			g[i][j]=g[g[i][j-1]][j-1];               //倍增处理,划重点
		}
	}
	for(int i=0;i<n;i++){
		int p=i,cnt=0,win=0;        //总局数和赢的局数
		for(int j=19;j>=0;j--) if(cnt+p2[j]<2*b){
			win+=f[p][j];
			cnt+=p2[j];
			p=g[p][j];
			if(win>=b || cnt-win>=b) break;
		}
		if(win>=b) cout << 1;
		else cout << 0;
	}
}

补24牛客多校 三 D 构造                                   牛客传送门

题意:给 n个pair值,要求顺序排列使得 a[ i ].second != a[ i+1 ].first对于 i<n成立

思路:若存在数字出现 >n+1次必定无解,否则有解

若无pair值 x==y,则随便放都能满足条件,所以先考虑 x==y的pair怎么放

将x==y的pair放入堆中,每次放置优先考虑堆中最多的pair值

代码如下:

const int N=2e5+10;
const int INF=0x3f3f3f3f;
const int mod=1e9+7;
ll n;
PII a[N];int idx;
void solve(){
	cin >> n;
	map<int,int>mp,sa; int ma=0;
	priority_queue<PII>pq;
	for(int i=1;i<=n;i++){
		int x,y; cin >> x >> y;
		if(x>y) swap(x,y);
		ma=max(++mp[x],ma);
		ma=max(++mp[y],ma);
		if(x==y) sa[x]++;
		else a[++idx].first=x,a[idx].second=y;
	}
	sort(a+1,a+idx+1);
	if(ma>n+1){cout << "NO\n"; return ;}
	else cout << "YES\n";
	for(auto [x,y]:sa) pq.push({y,x});
	int lst=0;
	while(!pq.empty()){
		vector<PII>ve;
		while(!pq.empty() && pq.top().second==lst){
			ve.push_back(pq.top());
			pq.pop();
		}
		if(!pq.empty()){
			auto [x,y]=pq.top(); pq.pop();
			cout << y << " " << y << "\n";
			lst=y;
			if(x>1) pq.push({x-1,y});
		}else{
			if(a[idx].first!=lst) cout << a[idx].first << " " << a[idx].second << "\n",lst=a[idx--].second;
			else cout << a[idx].second << " " << a[idx].first << "\n",lst=a[idx--].first;
		}
		for(auto [x,y]:ve) pq.push({x,y});
	}
	for(int i=1;i<=idx;i++){
		if(a[i].first!=lst) cout << a[i].first << " " << a[i].second << "\n",lst=a[i].second;
		else cout << a[i].second << " " << a[i].first << "\n",lst=a[i].first;
	}
}

星期二:

hdoj 4825 01trie                                            hdoj传送门

思路:在01字典树上贪心,此处01字典树和字典树没什么区别,就是二维大小为2

代码如下:

const int N=3.2e6+10;
const int INF=0x3f3f3f3f;
const int mod=1e9+7;
ll n;
int ch[N][2],idx;
ll num[N];
void insert(ll n){
	int p=0;
	for(int i=31;i>=0;i--){
		int b=n>>i&1;
		if(!ch[p][b]) ch[p][b]=++idx;
		p=ch[p][b];
	}
	num[p]=n;
}
ll fnd(ll x){
	int p=0;
	for(int i=31;i>=0;i--){
		int b=x>>i&1;
		if(ch[p][b^1]) p=ch[p][b^1];
		else p=ch[p][b];
	}
	return num[p];
}
void solve(){
	int tc; cin >> tc;
	for(int ii=1;ii<=tc;ii++){
		memset(ch,0,sizeof ch);
		idx=0;
		cout << "Case #" << ii << ":\n";
		int q; cin >> n >> q;
		for(int i=1;i<=n;i++){
			ll a; cin >> a;
			insert(a);
		}
		while(q--){
			ll s; cin >> s;
			cout << fnd(s) << "\n";
		}
	}
}

洛谷 P4551                                                     洛谷传送门

思路:树上任意两点 i,j异或路径等价于从1到 i异或路径 ^ 从1到 j异或路径,由异或原理易证

dfs时把从 1到所有点异或路径值存入 trie,然后遍历,对每个值在树上贪心找一遍,ans取max

代码如下:

const int N=3e6+10,M=210;
const int INF=0x3f3f3f3f;
const int mod=1e9+7;
ll n;
vector<PII>ve[N];
vector<int>ve2;
int ch[N][2],idx;
int num[N];
void insert(int x){
	int p=0;
	for(int i=31;i>=0;i--){
		int b=x>>i&1;
		if(!ch[p][b]) ch[p][b]=++idx;
		p=ch[p][b];
	}
	num[p]=x;
}
int fnd(int x){
	int p=0;
	for(int i=31;i>=0;i--){
		int b=x>>i&1;
		if(ch[p][b^1]) p=ch[p][b^1];
		else p=ch[p][b];
	}
	return x^num[p];
}
void dfs(int x,int f,int num){
	for(auto [w,v]:ve[x]) if(v!=f){
		ve2.push_back(num^w);
		insert(num^w);
		dfs(v,x,num^w);
	}
}
void solve(){
	cin >> n;
	for(int i=1;i<n;i++){
		int u,v,w; cin >> u >> v >> w;
		ve[u].push_back({w,v});
		ve[v].push_back({w,u});
	}
	dfs(1,0,0);
	int ans=0;
	for(int i:ve2) ans=max({fnd(i),i,ans});
	cout << ans;
}

cf testing round 19 C2                                      cf传送门

思路:哈希板子题,但若用ull会出现冲突,我是在wa了后改成双哈希,实测只用base=13331和mod=1e9+7的单哈希也不会wa,看来ull自动取模还是不太稳健

代码如下:

const int N=2e6+10;
const int INF=0x3f3f3f3f;
const int mod=1e9+7;
ll n;
const int base1=131,base2=13331;
ull ha1[N],p1[N];
ll ha2[N],p2[N];
void solve(){
	string s; cin >> s;
	n=s.size(); s=" "+s;
	p1[0]=p2[0]=1;
	for(int i=1;i<=n;i++){
		p1[i]=p1[i-1]*base1;
		ha1[i]=ha1[i-1]*base1+s[i];
		p2[i]=p2[i-1]*base2%mod;
		ha2[i]=(ha2[i-1]*base2+s[i])%mod;
	}
	for(int i=n/2+1;i<n;i++){
		ull hash1=ha1[n]-ha1[n-i]*p1[i];
		ll hash2=(ha2[n]-ha2[n-i]*p2[i]%mod+mod)%mod;
		if(ha1[i]==hash1 && ha2[i]==hash2){cout << "YES\n" << s.substr(1,i); return ;}
//		if(ha2[i]==hash2){cout << "YES\n" << s.substr(1,i); return ;}       //实测可用
	}
	cout << "NO";
}

星期三:

cf round 970 div3 G gcd                                  cf传送门

思路:注意这个操作能使所有数变为全部的gcd,那么贪心地放a数组应为0, gcd, 1*gcd, 2*gcd ...然后mex可O(1)求,但我用了个二分

代码如下:

ll n;
void solve(){
	ll k; cin >> n >> k;
	ll gc=0;
	for(int i=1;i<=n;i++){
		ll a; cin >> a;
		gc=__gcd(gc,a);
	}
	if(n==1){
		if(gc>=k) cout << k-1 << "\n";
		else cout << k << "\n";
		return ;
	}
	if(gc==1) cout << n-1+k << "\n";
	else{
		ll l=k,r=1e9+2e5,res=0;
		while(l<=r){
			ll mid=l+r>>1;
			if(min((mid-1+gc-1)/gc,n)+k>=mid) res=mid,l=mid+1;
			else r=mid-1;
		}
		cout << res-1 << "\n";
	}
}

cf round 970 div3 H                                     cf传送门

思路:首先想到对于所有>=x的值都将其取模,那么对于数num,范围 k*x - k*x+num ( k=0,1,2,3...的数都会小于等于 num,此范围内的数可以用桶数组+前缀和快速求得

按照题目给出的中位数的定义,需要n个数中除去自己后>=n/2个数,二分中位数,枚举范围进行check,复杂度方面,先不看二分,为调和级数的复杂度,再加个二分即 O(n*log^2)

代码如下:

onst int N=2e6+10,M=210;
const int INF=0x3f3f3f3f;
const int mod=1e9+7;
ll n;
int b[N],ans[N];
void solve(){
	int q; cin >> n >> q;
	for(int i=1;i<=n;i++) b[i]=0;
	for(int i=1;i<=n;i++){
		int a; cin >> a;
		b[a]++;
	}
	for(int i=1;i<=n;i++) b[i]+=b[i-1];
	for(int x=1;x<=n;x++){
		int l=0,r=x,res=0;
		while(l<=r){
			int mid=l+r>>1;
			ll sum=b[mid];
			for(int k=1;k*x<=n;k++) sum+=b[min(1ll*k*x+mid,n)]-b[k*x-1];
			if(sum-1>=n/2) res=mid,r=mid-1;   //注意和n取min,以免越界
			else l=mid+1;
		}
		ans[x]=res;
	}
	while(q--){
		int x; cin >> x;
		cout << ans[x] << " ";
	}
	cout << "\n";
}

cf round 969 div2 B                                    cf传送门

思路:简单题,只是觉得有点意思,刚开始还想到了动态开点权值线段树

其实因为操作很弱,只需一个变量维护最大值就可以了

代码如下:

ll n;
void solve(){
	int m; cin >> n >> m;
	ll ma=0;
	for(int i=1;i<=n;i++){
		int a; cin >> a;
		ma=max(1ll*a,ma);
	}
	while(m--){
		char op;int l,r; cin >> op >> l >> r;
		if(l<=ma && r>=ma) op=='+'?ma++:ma--;
		cout << ma << " ";
	}
	cout << "\n";
}

星期四:

cf round 971 div4 G1                                       cf传送门

思路:对一段序列修改最少值让序列满足逐一加一,可使 a【i】-= i,就转化为找一段序列中出现最多的值,可用值域线段树,但其实没必要,一个map和multiset也可实现动态维护

代码如下:

const int N=2e6+10;
const int INF=0x3f3f3f3f;
const int mod=1e9+7;
ll n;
int a[N];
int ans[N];
void solve(){
	int k,q; cin >> n >> k >> q;
	map<int,int>mp,vi;
	multiset<int>mt;
	for(int i=1;i<=n;i++){
		cin >> a[i];
		a[i]-=i;
	}
	int ma=0;
	for(int i=1;i<=k;i++) ma=max(++mp[a[i]],ma);
	ans[1]=k-ma;
	for(int i=1;i<=n;i++) if(!vi[a[i]]) mt.insert(mp[a[i]]),vi[a[i]]=1;
	for(int i=2;i+k-1<=n;i++){
		mt.erase(mt.find(mp[a[i-1]]));
		mt.insert(--mp[a[i-1]]);
		mt.erase(mt.find(mp[a[i+k-1]]));
		mt.insert(++mp[a[i+k-1]]);
		ans[i]=k-*(--mt.end());
	}
	while(q--){
		int l,r; cin >> l >> r;
		cout << ans[l] << "\n";
	}
}

星期五:

cf round 971 div4 G2                                     cf传送门

思路:此题的c【i】为G1中的ans【i】,f 和 g为倍增数组

p【i】为 i右边离 i最近且 c【p【i】】< c【i】的点,可单调栈处理,记 i到 p【i】为一步,这一步的贡献即为f【i】【0】= (p【i】- i )* c【i】,可知从 i到 p【i】-1的点的贡献都为 c【i】,那么计算 l到r-k+1的总贡献即可用倍增

注意倍增时不要超出 r-k+1 的范围

代码如下:

const int N=2e5+10;
const int INF=0x3f3f3f3f;
const int mod=1e9+7;
ll n;
int a[N];
int c[N];
ll f[N][20],g[N][20];
void solve(){
	int k,q; cin >> n >> k >> q;
	map<int,int>mp,vi;
	multiset<int>mt;
	for(int i=1;i<=n;i++){
		cin >> a[i];
		a[i]-=i;
	}
	int ma=0;
	for(int i=1;i<=k;i++) ma=max(++mp[a[i]],ma);
	c[1]=k-ma;
	for(int i=1;i<=n;i++) if(!vi[a[i]]) mt.insert(mp[a[i]]),vi[a[i]]=1;
	for(int i=2;i+k-1<=n;i++){
		mt.erase(mt.find(mp[a[i-1]]));
		mt.insert(--mp[a[i-1]]);
		mt.erase(mt.find(mp[a[i+k-1]]));
		mt.insert(++mp[a[i+k-1]]);
		c[i]=k-*(--mt.end());
	}
	stack<int>sk;
	for(int i=n;i>n-k+1;i--) g[i][0]=n;
	for(int i=n-k+1;i;i--){
		while(!sk.empty() && c[sk.top()]>=c[i]) sk.pop();
		if(sk.empty()) g[i][0]=n;
		else g[i][0]=sk.top();
		sk.push(i);
		f[i][0]=1ll*(g[i][0]-i)*c[i];
	}
	for(int j=1;j<20;j++){
		for(int i=1;i<=n;i++){
			f[i][j]=f[i][j-1]+f[g[i][j-1]][j-1];
			g[i][j]=g[g[i][j-1]][j-1];
		}
	}
	while(q--){
		int l,r; cin >> l >> r; r-=k-1;
		ll pos=l,ans=0;
		for(int j=19;j>=0;j--) if(g[pos][j]<=r){   //pos必须<=r
			ans+=f[pos][j];
			pos=g[pos][j];
		}
		ans+=(r-pos+1ll)*c[pos];
//		cout << l << " " << r << "\n";
		cout << ans << "\n";
	}
}

cf round 969 div2 C                                      cf传送门

思路:首先易想到操作等价于使任意数+=或-=gcd(a,b),那么答案一定小于gcd

先让所有数对gcd取模,排序后答案取 c【n】- c【1】,再遍历一遍,使 c【i-1】+= gc成为最大值,和此时最小值 c【i】作差,ans取min

代码如下:

const int N=2e5+10;
const int INF=0x3f3f3f3f;
const int mod=1e9+7;
ll n;
ll c[N];
void solve(){
	ll a,b; cin >> n >> a >> b;
	ll gc=__gcd(a,b);
	for(int i=1;i<=n;i++){
		cin >> c[i];
		c[i]%=gc;
	}
	sort(c+1,c+n+1);
	ll ans=c[n]-c[1];
	for(int i=2;i<=n;i++) ans=min(c[i-1]+gc-c[i],ans);
	cout << ans << "\n";
}

补 24牛客多校 四 I                                           牛客传送门

唉唉,还是太菜

思路:注意到若 【l,r】满足完全区间,则【l+1,r】也一定是完全区间,换句话说 l增加时,r 一定不减,由此可以想到双指针,对完全区间【l,r】,判断【l,r+1】是否完全即判断 r+1是否和    l 到 r每个点都连了边,可以对右端点存左端点,然后二分来实现判断

代码如下:

const int N=2e6+10;
const int INF=0x3f3f3f3f;
const int mod=1e9+7;
ll n;
vector<int>ve[N];
void solve(){
	int m; cin >> n >> m;
	for(int i=1;i<=m;i++){
		int u,v; cin >> u >> v;
		ve[v].push_back(u);
	}
	ll ans=0;
	for(int i=2;i<=n;i++) sort(ve[i].begin(),ve[i].end());  //别漏了排序
	for(int i=1,j=1;i<=n;i++){
		j=max(i,j);
		while(j<n && ve[j+1].end()-lower_bound(ve[j+1].begin(),ve[j+1].end(),i)==j-i+1) j++;
		ans+=j-i+1;
	}
	cout << ans;
}

补 24牛客多校 四 C                                        牛客传送门

思路:看到排列首先考虑处理出环。发现操作等价于将环长度减3,最后一步可以减4,若环长度%3为1或为0都直接通过减3减4消除,若%3为2,注意操作可以一次处理两个长为2的环,所以统计cnt,最后加上 cnt/2 向上取整

代码如下:

const int N=2e6+10;
const int INF=0x3f3f3f3f;
const int mod=1e9+7;
ll n;
int p[N];
bool vi[N];
int len;
void dfs(int x){
	if(vi[x]) return ;
	vi[x]=1;
	len++;
	dfs(p[x]);
}
void solve(){
	cin >> n;
	for(int i=1;i<=n;i++){
		cin >> p[i];
		vi[i]=0;
	}
	int cnt2=0,ans=0;
	for(int i=1;i<=n;i++) if(!vi[i] && p[i]!=i){
		len=0;
		dfs(i);
		ans+=len/3;
		if(len%3==2) cnt2++;
	}
	ans+=(cnt2+1)/2;
	cout << ans << "\n";
}

星期六:

补 24牛客多校4 H                                          牛客传送门

思路: 抽象的操作,近似于猜结论题,但雨巨讲的非常好

【【雨巨讲题】2024牛客暑期多校第四场新手向讲题】

代码如下:

const int N=2e6+10;
const int mod=1e9+7;
ll n;
ll a[N];
void solve(){
    cin >> n;
    for(int i=1;i<=n;i++) cin >> a[i];
    sort(a+1,a+n+1);
    ll gc=0;
    for(int i=1;i<n;i++) gc=__gcd(a[i+1]-a[i],gc);
    cout << gc << "\n";
}

周日:

CCPC网络赛打的是依托, 打到中途和铭轮流睡大觉有点蚌埠住

补24 牛客多校4 A                                       牛客传送门

思路: 注意读题,题面给了几个关键信息,一是给边形式为a,b且a是b的父亲,代表着不用双边建树,二更为关键,询问的c不会是之前的b,代表着c必定是一棵树的根,因为没当过儿子

虽是第一次见,但应该说是比较典的带权并查集,可以维护一棵树的深度,dep【i】代表其到      fa【i】的距离,输入a,b时将dep【b】初始化为1,修改下fnd函数,使其在找祖宗时dep也会变,然后在连边时,对 a的祖宗an为根的树的深度取个max

说下改fnd函数时写错的地方,第一次写把对于dep的处理写为了dep[x]=dep[fa[x]]+1,实际应为dep[x]+=dep[fa[x]],为什么+1错了呢。举个栗子,一长为2的链,根节点为a,叶子为b,此时b的深度即dep【b】为2,fa【b】是a,如果输入an,a,将a作为an的儿子,那么fa【a】=an,a的深度变为dep【a】=1,在压缩时,如果写的+1,那么dep【b】=dep【a】+1 =2,就会出错,,所以对深度的处理应为+=dep【fa【x】】

代码如下:

const int N=2e6+10,M=210;
const int INF=0x3f3f3f3f;
const int mod=1e9+7;
ll n;
int fa[N];
int dep[N],ans[N];
int fnd(int x){
	if(fa[x]==x) return x;
	int an=fnd(fa[x]);
	dep[x]+=dep[fa[x]];      //注意此处应为+=
	return fa[x]=an;
}
void solve(){
	cin >> n;
	for(int i=1;i<=n;i++) fa[i]=i,ans[i]=dep[i]=0;
	for(int i=1;i<n;i++){
		int a,b,c; cin >> a >> b >> c;
		int rt=fnd(a);
		ans[rt]=max(ans[b]+dep[a]+1,ans[rt]);
		fa[b]=a,dep[b]=1;    //初始化
		cout << ans[c] << " \n"[i==n-1];
	}
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值