6月の题

想了想还是写一下。

P5049 [NOIP2018 提高组] 旅行 加强版

弱化版是 \(O(n^{\mathbf{2}})\) 可以过的,直接枚举不走哪条边即可。对于强化版考虑去模拟我们在环上的决策,除了正常遍历就是走到环的某一处便直接回溯先走更小的点。这样的话可以先把环找出来,dfs 的时候先把环走完,如果在环的另一边的点比下一个要走到的点小,那么就回溯过去,然后正常 dfs 即可。相比现在题单里的蓝题都算是简单易懂了。

点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define ll int
const ll N=500005,M=1919810,inf=2147483646;
struct xx{
	ll next,to;
}e[2*N];
ll head[2*N],cnt;
void add(ll x,ll y){
	e[++cnt].next=head[x];
	e[cnt].to=y;
	head[x]=cnt;
}
ll n,m;
struct edge{
	ll a,b;
}a[2*N];
bool cmp(edge x,edge y){
	return x.b>y.b;
}
void dfs1(ll u,ll fa){
	cout<<u<<" ";
	for(int i=head[u];i;i=e[i].next){
		ll v=e[i].to;
		if(v==fa) continue;
		dfs1(v,u);
	}
}
bool vis[N],huan[N],flag;
ll f[N];
void dfs2(ll u,ll fa){
	if(flag) return;
	if(!f[u]) f[u]=fa;
	else if(f[u]!=fa){
		while(fa!=u){
			huan[fa]=1;
			fa=f[fa];
		}
		huan[u]=flag=1;
		return;
	}
	for(int i=head[u];i;i=e[i].next){
		ll v=e[i].to;
		if(v==fa) continue;
		dfs2(v,u);
	}
}
ll pos=inf;
bool fl_back;
void dfs3(ll u){
	vis[u]=1;
	cout<<u<<" ";
	if(huan[u]){
		bool fl=0;
		for(int i=head[u];i;i=e[i].next){
			ll v=e[i].to;
			if(fl_back) break;
			if(vis[v]) continue;
			if(huan[v]){
				i=e[i].next;
				while(vis[e[i].to]) i=e[i].next;
				if(i) pos=e[i].to;
				else if(v>pos) fl=1,fl_back=1;
				break;
			}
			//break被恰到这里明显是不对的 
		}
		for(int i=head[u];i;i=e[i].next){
			ll v=e[i].to;
			if(vis[v]||(huan[v]&&fl)) continue;
			dfs3(v);
		}
		return;
	}
	for(int i=head[u];i;i=e[i].next){
		ll v=e[i].to;
		if(vis[v]) continue;
		dfs3(v);
	}
}
int main(){
	ios::sync_with_stdio(0);
	cin.tie(0); cout.tie(0);
	cin>>n>>m;
	for(int i=1;i<=m;++i){
		cin>>a[i].a>>a[i].b;
		a[i+m].a=a[i].b;
		a[i+m].b=a[i].a;
	}
	sort(a+1,a+2*m+1,cmp);
	for(int i=1;i<=2*m;++i) add(a[i].a,a[i].b);
	if(m==n-1){
		dfs1(1,0);
		return 0;
	}
	dfs2(1,1);
	flag=fl_back=0;
	dfs3(1);
	return 0;
}//我服了爸爸 

CF1625D

首先要学会正确地给01trie开空间,并且正确地建出01trie。
然后要会正确地遍历01trie,并且明确要求什么。
找到 \(k\) 二进位最高位 \(\mathbf{1}\) 在01trie中的层数,然后选这些点及其子树中的点(相当于选数),注意如果一个点有左右儿子那么就要在左右儿子中各选一个,否则在其子树中随便选。但实际上我是██的,所以分讨还不是太懂,不过记住01trie上的很多信息都和深度有关。

点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define ls trie[u][0]
#define rs trie[u][1]
const ll N=3*114514,M=1919810;
ll n,k,a[N],maxn,lim;
ll trie[30*N][2],tot,f[30*N];
/*void build(ll val,ll id){
	ll rt=0;
	for(int i=(1<<29);i;i>>=1){
		ll nex=val&i;
		if(!trie[rt][nex]) trie[rt][nex]=++tot;
		rt=trie[rt][nex];
	}
	f[rt]=id;
}宝了个贝的*/
void build(ll val,ll id){
	ll rt=0;
	for(int i=29;i>=0;--i){
		ll nex=((val>>i)&1);
		if(!trie[rt][nex]) trie[rt][nex]=++tot;
		rt=trie[rt][nex];
	}
	f[rt]=id;
}
vector <ll> out;
void calc(ll u,ll dept){
	if(dept==-1){
		out.push_back(f[u]);
		return;
	}
	if(ls) calc(ls,dept-1);
	else calc(rs,dept-1);
}
bool fl;
void calc2(ll l,ll r,ll dept,ll val){
	if(fl||k>val+(1<<(dept+1))-1) return;
	if(dept==-1){
		if(val>=k){
			fl=1;
			out.push_back(f[l]);
			out.push_back(f[r]);
		}
		return;
	}
	if(trie[l][0]&&trie[r][1]) calc2(trie[l][0],trie[r][1],dept-1,val|(1<<dept));
	if(trie[l][1]&&trie[r][0]) calc2(trie[l][1],trie[r][0],dept-1,val|(1<<dept));
	if(trie[l][0]&&trie[r][0]) calc2(trie[l][0],trie[r][0],dept-1,val);
	if(trie[l][1]&&trie[r][1]) calc2(trie[l][1],trie[r][1],dept-1,val);
}
void dfs(ll u,ll dept){
	if(dept==lim){
		if(ls&&rs){
			fl=0;
			calc2(ls,rs,dept-1,(1<<lim));
			if(!fl) calc(ls,dept-1);
		}
		else{
			if(ls) calc(ls,dept-1);
			if(rs) calc(rs,dept-1);
		}
		return;
	}
	if(ls) dfs(ls,dept-1);
	if(rs) dfs(rs,dept-1);
}
int main(){
	ios::sync_with_stdio(0);
	cin.tie(0); cout.tie(0);
	cin>>n>>k;
	for(int i=1;i<=n;++i){
		cin>>a[i],build(a[i],i);
		maxn=max(maxn,a[i]);
	}
	if(k==0){
		cout<<n<<'\n';
		for(int i=1;i<=n;++i) cout<<i<<" ";
		return 0;
	}
	lim=log2(k);
	if((1<<lim)>maxn){
		cout<<-1;
		return 0;
	}
	//cout<<"?: "<<tot<<'\n';
	dfs(0,29);
	if(out.size()<2){
		cout<<-1;
		return 0;
	}
	cout<<out.size()<<'\n';
	for(int i:out) cout<<i<<" ";
	return 0;
}//不会01trie紫菜

CF178F3

这个可以对串排序后求 \(LCP(a_i,a_{i-\mathbf{1}})\)(也就是 \(height\)),然后建笛卡尔树做树上背包求出,直观但不会。
树上背包是要的。可以考虑先建出trie树,记录下每个节点的 \(siz\),然后在trie树上作 \(O(n^{\mathbf{2}})\) 的树上背包,总复杂度 \(O(|a|n^{\mathbf{2}})\),竟然能过。
\(dp[u][i]\) 为在 \(u\) 子树内 选 \(i\) 个点时的最大答案,转移:\(dp[u][i]=max(dp[u][i],dp[u][i-j]+dp[t[u][nex]][j]+(i-j)*j*dept)\)
但是有一个问题,trie树的节点数非常多,是无法以其节点编号作为 dp 数组的下标的。古神有一个很巧妙地解决方法:垃圾回收。开一个栈,塞 \(\mathbf{1\sim600}\) 进去,然后有节点需要 dp 时就让它映射到这个编号上,dp 完了再把这个编号放回栈中,因为一个串最长只有 \(\mathbf{500}\) 位,很妙。
其实本来是要建虚树或者树剖优化的,但是我菜都不会。

点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define ls t[u][0]
#define rs t[u][1]
const ll N=200010,M=1919810,mod=998244353;
ll qpow(ll a,ll b){
	ll ans=1;
	a%=mod;
	while(b){
		if(b&1) ans=ans*a%mod;
		a=a*a%mod;
		b>>=1;
	}
	return ans;
}
ll T;
ll n,a[N],ans;
ll t[30*N][2],tot,f[30*N];
void build(ll val){
	ll rt=0;
	for(ll i=29;i>=0;--i){
		ll nex=((val>>i)&1);
		if(!t[rt][nex]) t[rt][nex]=++tot;
		rt=t[rt][nex];
		++f[rt];
	}
}
void dfs(ll u,ll dept){
	if(!ls&&!rs){
		ans=(ans+f[u]*f[u]%mod*(dept+1)%mod)%mod;
		return;
	}
	if(ls&&rs) ans=(ans+f[ls]*f[rs]*(2*dept+3)%mod)%mod;
	if(ls) dfs(ls,dept);
	if(rs) dfs(rs,dept+1);
}
void solve(){
	for(int i=0;i<=tot;++i) t[i][0]=t[i][1]=f[i]=0;
	tot=ans=0;
	cin>>n;
	for(int i=1;i<=n;++i) cin>>a[i],build(a[i]);
	dfs(0,0);
	cout<<ans*qpow(n*n,mod-2)%mod<<'\n';
}
int main(){
	ios::sync_with_stdio(0);
	cin.tie(0); cout.tie(0);
	cin>>T;
	while(T--) solve();
	return 0;
}

AGC044C

01trie升级版:三进制 012trie。这个暂时不想写,真的不会做trie的题了。

点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define s0 t[u][0]
#define s1 t[u][1]
#define s2 t[u][2]
const ll N=6377292+5,M=1919810;
string s;
ll n,t[N][3],f[N],tot;
//傻宝三进制
ll num[13],cnt;
ll ans[N],tag[N];
void build(ll val){
	ll x=val,tmp=pow(3,n-1);
	cnt=0;
	while(tmp){
		for(int i=2;i>=0;--i)
			if(i*tmp<=x){
				x-=i*tmp;
				tmp/=3;
				num[++cnt]=i;
				break;
			}
	}
	ll rt=0;
	for(int i=cnt;i>=1;--i){
		ll nex=num[i];
		if(!t[rt][nex]) t[rt][nex]=++tot;
		rt=t[rt][nex];
	}
	f[rt]=val;
}
//真不会trie了 
void pushdown(ll u){
	if(!tag[u]) return;
	tag[s0]^=1,tag[s1]^=1,tag[s2]^=1,tag[u]^=1;
	swap(s1,s2);
}
void update(ll u,ll dept){
	if(!dept) return;
	pushdown(u);
	ll x=s0,y=s1,z=s2;
	s0=z,s1=x,s2=y;
	update(s0,dept-1);
}
void dfs(ll u,ll dept,ll tmp,ll pos){
	if(!dept){
		ans[f[u]]=pos;
		return;
	}
	pushdown(u);
	dfs(s0,dept-1,tmp*3,pos);
	dfs(s1,dept-1,tmp*3,pos+tmp);
	dfs(s2,dept-1,tmp*3,pos+2*tmp);
}
int main(){
	ios::sync_with_stdio(0);
	cin.tie(0); cout.tie(0);
	cin>>n>>s;
	ll m=s.size();
	for(int i=0;i<pow(3,n);++i) build(i);
	for(int i=0;i<m;++i)
		if(s[i]=='S') tag[0]^=1;
		else update(0,n);
	dfs(0,n,1,0);
	for(int i=0;i<pow(3,n);++i) cout<<ans[i]<<" ";
	return 0;
}

P1558 色板游戏

水题,开 \(\mathbf{30}\) 棵线段树维护每种颜色即可。但是最后一个卡 ODT 的点有点毒瘤,线段树的每个节点只能存两个 int 的变量,要不然就MLE,所以 \(l,r\) 就直接放到递归里了。

加强版请认准 \(Ynoi\)

点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define ll int
#define ls now<<1
#define rs now<<1|1
const ll N=100001,M=1919810;
ll n,m,T;
struct tree{
	ll sum;
	bool fl,tag;
}t[30][1<<18];
void pushup(ll now,tree *t){
	t[now].sum=t[ls].sum+t[rs].sum;
}
void pushdown(ll now,ll l,ll r,tree *t){
	ll k=t[now].tag,mid=(l+r)>>1;
	if(!t[now].fl) return;
	t[ls].sum=k*(mid-l+1),t[ls].tag=k;
	t[rs].sum=k*(r-mid),t[rs].tag=k;
	t[ls].fl=t[rs].fl=1;
	t[now].fl=0;
}
void build(ll now,ll l,ll r,tree *t){
	if(l==r){
		t[now].sum=t[now].tag=t[now].fl=0;
		return;
	}
	ll mid=(l+r)>>1;
	build(ls,l,mid,t);
	build(rs,mid+1,r,t);
	pushup(now,t);
}
void update(ll now,ll l,ll r,ll x,ll y,ll k,tree *t){
	if(l>=x&&r<=y){
		t[now].sum=k*(r-l+1);
		t[now].tag=k,t[now].fl=1;
		return;
	}
	pushdown(now,l,r,t);
	ll mid=(l+r)>>1;
	if(x<=mid) update(ls,l,mid,x,y,k,t);
	if(y>mid) update(rs,mid+1,r,x,y,k,t);
	pushup(now,t);
}
ll query(ll now,ll l,ll r,ll x,ll y,tree *t){
	if(l>=x&&r<=y) return t[now].sum;
	pushdown(now,l,r,t);
	ll mid=(l+r)>>1,ans=0;
	if(x<=mid) ans+=query(ls,l,mid,x,y,t);
	if(y>mid) ans+=query(rs,mid+1,r,x,y,t);
	return ans;
}
int main(){
	ios::sync_with_stdio(0);
	cin.tie(0); cout.tie(0);
	cin>>n>>T>>m;
	for(int i=1;i<=T;++i) build(1,1,n,t[i-1]);
	update(1,1,n,1,n,1,t[0]);
	bool flag=0;
	ll las=0,lasl=0,lasr=0;
	while(m--){
		char ch;
		ll l,r,k;
		cin>>ch>>l>>r;
		if(l>r) swap(l,r);
		if(ch=='C'){
			cin>>k;
			//cout<<"AWA\n";
			update(1,1,n,l,r,1,t[k-1]);
			//cout<<"QWQ???\n";
			for(int i=1;i<=T;++i){
				if(i==k) continue;
				update(1,1,n,l,r,0,t[i-1]);
			}
			flag=0;
		}
		else{
			if(flag&&l==lasl&&r==lasr){
				cout<<las<<'\n';
				continue;
			}
			ll ans=0;
			for(int i=1;i<=T;++i)
				if(query(1,1,n,l,r,t[i-1])>0) ++ans;
			las=ans,lasl=l,lasr=r,flag=1;
			cout<<ans<<'\n';
		}
	}
	return 0;
}//毒瘤,太毒瘤了 

ARC105C Camels and Bridge

奇怪的题。看到 \(n\) 只有 \(\mathbf{8}\),且骆驼是要考虑顺序的,所以可以直接枚举 \(n\) 的全排列,然后找出最小答案。
现在骆驼已经排好顺序了。为了让一段骆驼能走过所有桥,我们要找到一段最长的承担不起这段骆驼重量的桥。可以考虑对桥按承重量排序,记录长度的前缀最大值,然后就可以二分得到要找的桥的长度,记为 \(len\)
至于骆驼的总长度可以用 dp 求出,设 \(dp_i\) 表示前 \(i\) 只骆驼能通过所有桥的最小总长度,但我们在转移的时候要优先满足方案的可行性,所以转移是 \(dp_i=\mathtt{max}(dp_i,dp_j+len)\),最后对所有答案取个 \(\mathtt{min}\) 就行。

点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const ll N=114514,M=1919810,inf=2147483646;
ll n,m,a[9],id[9],sum[9],maxd[N],dp[9]; //前i个s所需的总距离
ll ans=inf,maxn;
struct xx{
	ll l,v;
}b[N];
bool cmp(xx x,xx y){
	return x.v<y.v;
}
int main(){
	ios::sync_with_stdio(0);
	cin.tie(0); cout.tie(0);
	cin>>n>>m;
	for(int i=1;i<=n;++i) cin>>a[i],maxn=max(maxn,a[i]);
	for(int i=1;i<=m;++i){
		cin>>b[i].l>>b[i].v;
		if(maxn>b[i].v){
			cout<<-1;
			return 0;
		}
	}
	sort(b+1,b+m+1,cmp);
	for(int i=1;i<=m;++i) maxd[i]=max(maxd[i-1],b[i].l);
	for(int i=1;i<=n;++i) id[i]=i;
	do{
		for(int i=1;i<=n;++i) sum[i]=sum[i-1]+a[id[i]],dp[i]=0;
		for(int i=1;i<=n;++i){
			for(int j=1;j<=i;++j){
				ll val=sum[i]-sum[j-1];
				ll l=1,r=m,pos=0;
				while(l<=r){
					ll mid=(l+r)>>1;
					if(b[mid].v<val) l=mid+1,pos=mid;
					else r=mid-1;
				}
				dp[i]=max(dp[i],dp[j]+maxd[pos]);
			}
		}
		ans=min(ans,dp[n]);
	}while(next_permutation(id+1,id+n+1));
	cout<<ans;
	return 0;
}

ARC132D

只要找到关键结论就很好理解且很好做了,但我傻。

关键结论:中间串的 \(\mathbf{1}\) 位置要在 \(a,b\) 串中相同排名的 \(\mathbf{1}\) 的位置之间,比如 \(a\) 串中第一个 \(\mathbf{1}\)\(i=\mathbf{2}\)\(b\) 中第一个 \(\mathbf{1}\)\(i=\mathbf{5}\),那么中间串中第一个 \(\mathbf{1}\) 的位置就该在 \((\mathbf{2,5})\)
然后我们用一个单指针扫一遍,给扫到的位置赋为 \(\mathbf{1}\) 就行了。不过注意还要考虑 \(\mathbf{0}\) 的贡献,所以第一个 \(\mathbf{1}\) 可以放在第一段选择区间的末尾或是 \(i=\mathbf{1}\)

点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const ll N=3*114514,M=1919810,mod=1e9+7;
ll n,m,len;
char a[N],b[N];
ll posa[N],posb[N],cnta,cntb;
ll x[N],y[N],ans;
bool fl[N];
//新串的 1 位置要在ab串相同排名的 1 的位置之间
//尽量连续,不断++pos 
void calc(ll st){
	ll pos=st;
	memset(fl,0,sizeof(fl));
	fl[pos]=1;
	for(int i=2;i<=m;++i){
		if(x[i]<=pos+1&&y[i]>=pos+1) ++pos;
		else pos=y[i];
		fl[pos]=1;
	}
	ll res=0;
	for(int i=1;i<len;++i)
		if(fl[i]==fl[i+1]) ++res;
	ans=max(ans,res);
}
int main(){
	ios::sync_with_stdio(0);
	cin.tie(0); cout.tie(0);
	cin>>n>>m,len=n+m;
	for(int i=1;i<=len;++i){
		cin>>a[i];
		if(a[i]=='1') posa[++cnta]=i;
	}
	for(int i=1;i<=len;++i){
		cin>>b[i];
		if(b[i]=='1') posb[++cntb]=i;
	}
	for(int i=1;i<=m;++i)
		x[i]=min(posa[i],posb[i]),y[i]=max(posa[i],posb[i]);
	if(x[1]==1) calc(x[1]);
	calc(y[1]);
	cout<<ans;
	return 0;
}//今天的题咋都这么短小精悍 

P4396 [AHOI2013] 作业

题单里的做不出来了,来水这个。
题意:每次询问给定 \(l,r,a,b\),求数列上的区间 \([l,r]\) 中值域在 \([a,b]\) 中的 数的个数 和 不同的数的个数。这个就是明显要值域分块了,我们开三个数组:\(cnt\) 记每个数的个数,用于散块;\(num\) 记每个值域块中的数的个数,用于整块;\(tot\) 记每个值域块中不同的数的个数,用于整块。简单维护这三个数组就能直接查询了。

点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define ll int
const ll N=114514,M=1919810;
ll n,m,a[N],ns,nq,bel[N],st[N],ed[N];
ll num[N],cnt[N],tot[N];
ll out1[N],out2[N],ans1,ans2;/*
cnt记每个数的个数,用于散块 
num记每个值域块中的数的个数,用于整块 
tot记每个值域块中不同的数的个数,用于整块*/
struct xx{
	ll l,r,a,b,id;
}q[N],out[N];
bool cmp(xx x,xx y){
	return bel[x.l]^bel[y.l]?bel[x.l]<bel[y.l]:((bel[x.l]&1)?x.r<y.r:x.r>y.r);
}
void add(ll x){
	++num[bel[a[x]]],++cnt[a[x]];
	if(cnt[a[x]]==1) ++tot[bel[a[x]]];
}
void del(ll x){
	--num[bel[a[x]]],--cnt[a[x]];
	if(!cnt[a[x]]) --tot[bel[a[x]]];
}
void calc(ll A,ll B){
	ans1=ans2=0;
	if(bel[A]==bel[B]){
		for(int i=A;i<=B;++i)
			ans1+=cnt[i],ans2+=(cnt[i]>0);
		return;
	}
	for(int i=A;i<=ed[bel[A]];++i) ans1+=cnt[i],ans2+=(cnt[i]>0);
	for(int i=st[bel[B]];i<=B;++i) ans1+=cnt[i],ans2+=(cnt[i]>0);
	for(int i=bel[A]+1;i<bel[B];++i) ans1+=num[i],ans2+=tot[i];
}
int main(){
	ios::sync_with_stdio(0);
	cin.tie(0); cout.tie(0);
	cin>>n>>m; ns=max(n/sqrt(m),1.0),nq=ceil(n*1.0/ns); //啊咧? 
	for(int i=1;i<=nq;++i){
		st[i]=ns*(i-1)+1,ed[i]=min(ns*i,n);
		for(int j=st[i];j<=ed[i];++j)
			cin>>a[j],bel[j]=i;
	}
	for(int i=1;i<=m;++i) cin>>q[i].l>>q[i].r>>q[i].a>>q[i].b,q[i].id=i;
	sort(q+1,q+m+1,cmp);
	ll l=1,r=0;
	for(int i=1;i<=m;++i){
		while(l>q[i].l) add(--l);
		while(l<q[i].l) del(l++);
		while(r>q[i].r) del(r--);
		while(r<q[i].r) add(++r);
		calc(q[i].a,q[i].b);
		out1[q[i].id]=ans1;
		out2[q[i].id]=ans2;
	}
	for(int i=1;i<=m;++i) cout<<out1[i]<<" "<<out2[i]<<'\n';
	return 0;
}

想了想还是写在这上面好看一点。

P1453 城市环路

这是一棵基环树。先考虑普通的树,可以简单树形 dp 做,设 \(dp_{i,0/1}\) 表示在 \(i\) 的子树中,选或不选 \(i\) 点开店时的最大价值,转移很简单:\(dp_{u,0}+=\max(dp_{v,0},dp_{v,1}),dp_{u,1}+=dp_{v,0}\)

然后考虑有环的情况,对基环树一个常规的想法是切断环上的某一条边然后再讨论情况。这个题中我们先随便记录环上某一条边的两个端点,记为 \(s,t\)。然后不走 \(s,t\) 这条边,分别以 \(s,t\) 为根进行一遍树形 dp,然后取 \(\max(dp_{s,0},dp_{t,0})\) 就是答案。为什么呢?感性理解,要是取 \(dp_{s/t,1}\) 的答案,那么也无法保证另一端的 \(s/t\) 是不是没取。

P8456 「SWTR-8」地地铁铁

这是一个无向连通图,要求统计点对数量。对于统计图上点对或路径信息的题目一般都会用到圆方树来将图转化成树。

然后考虑对点对情况分类讨论,当一对点在不同的点双中,当他们在圆方树上的路径经过的点双只有全是黑/白边的点双才不合法,否则一定合法。对于相同点双中的点,比较直观的不合法情况是长这样的:
也就是一边的边全是黑的,另一边全是白的,如果不是这样那么必然有解,剩下的就是怎么实现这些判断。

posted @ 2024-06-18 21:02  和蜀玩  阅读(26)  评论(0)    收藏  举报