24.7.21(Tarjan SCC缩点,警钟长鸣)

星期一:

睿抗初赛,遇到了个不会的判章鱼环,最后40min一直憋着de,最后差8分的段错误,感觉差点

星期二:

贴 ABC 160 E                                                           atc传送门

题意:吃a个红苹果和b个绿苹果,还有c个无色苹果可以平替任意颜色苹果,问最大值

思路:开局没想到优先队列,被折磨了一个多小时。。

补 ABC 172 D                                                          atc传送门

思路:质因数分解可以冲过去(<=2000ms,正解需要考虑到等差数列

考虑每个因子对答案的贡献,如 n==7,2是2,4,6的因子,对答案的贡献分别是2,4,6,可以发现这是一个等差数列,用等差数列公式计算即可

代码(分解质因数 如下:

const int N=1e7+10,M=210;
ll n;
int p[N],idx;
bool vi[N];
void getp(int x){
	for(int i=2;i<=x;i++){
		if(!vi[i]) p[++idx]=i;
		for(int j=1;1ll*i*p[j]<=x;j++){
			vi[i*p[j]]=1;
			if(i%p[j]==0) break;
		}
	}
}
void solve(){
	cin >> n;
	getp(n);
	ll ans=0;
	for(int i=1;i<=n;i++){
		ll f=1;int t=i;
		for(int j=1;j<=idx && p[j]<=t/p[j];j++){
			int tt=1;
			while(t%p[j]==0){
				t/=p[j];
				tt++;
			}
			f*=tt;
		}
		if(t>1) f*=2;
		ans+=f*i;
	}
	cout << ans;
}

代码(等差数列 如下:

ll n;
void solve(){
	cin >> n;
	ll ans=0;
	for(int i=1;i<=n;i++) ans+=(i+n/i*i)*(n/i)/2;
	cout << ans;
}

补 ABC 175 D                                                      atc传送门

思路:整个排列一定是由若干环组成的,目的是找一个起点在环里走【1,k】的步数获得最大分数

根据得分规则处理出 sc【i】【j】表示从 i点起走 j步获得的分数,再处理出单调不减性可省去后面的枚举操作。dfs找环并处理出每个点所在环的长度,最后即可枚举起点计算答案

计算答案时存在多种情况,总结下就是不一定走满 k步,我漏掉的情况是可走多圈时,最后一圈可不走完而取最优值

代码如下:

ll n;
ll c[5050],p[5050],len[5050];
ll sc[5050][5050];
bool vi[5050];
ll cnt;
inline void dfs(int x){
	if(vi[x]) return ;
	vi[x]=1;
	cnt++;
	dfs(p[x]);
	len[x]=cnt;
}
void solve(){
	ll k; cin >> n >> k;
	for(int i=1;i<=n;i++) cin >> p[i];
	ll mi=LLONG_MIN;
	for(int i=1;i<=n;i++) cin >> c[i],mi=max(c[i],mi);
	if(mi<0){cout << mi; return ;}
	for(int i=1;i<=n;i++){
		if(vi[i]) continue;
		cnt=0;
		dfs(i);
	}
	ll ma=LLONG_MIN;
	for(int j=1;j<=n;j++){
		for(int i=1;i<=n;i++){
			sc[i][j]=sc[p[i]][j-1]+c[p[i]];
			if(j<=k) ma=max(sc[i][j],ma);
		}
	}
	for(int i=1;i<=n;i++){
		for(int j=2;j<len[i];j++) sc[i][j]=max(sc[i][j-1],sc[i][j]);
	}                               //单调不减,若能走j步那么也能选择走j-1步
	for(int i=1;i<=n;i++){
		if(k<len[i]) ma=max(sc[i][k],ma);
		else if(k>=len[i]){
			ll cp=max(0ll,k/len[i]*sc[i][len[i]]);
			cp+=max(0ll,sc[i][k%len[i]]);
			ma=max(cp,ma);                             //走满

			cp=max(0ll,(k/len[i]-1)*sc[i][len[i]]);
			cp+=sc[i][len[i]-1];
			ma=max(cp,ma);                             //少走满一圈,最后一圈取最大
		}
	}
	cout << ma;
}

补 ABC 146 E                                                      atc传送门

思路:i到 j+1区间为合法序列的条件是 ( a【i】- a【j】)%k== i - j,不妨将右边也模上 k

移项后变为 (a【i】-i)%k==(a【j】- j)%k,用mp存下之前的(a【i】-i)%k的值即可

注意初始时mp【0】=1,且窗口有效长度<=k

代码如下:

const int N=2e5+10,M=210;
ll n;
ll a[N];
void solve(){
	ll k; cin >> n >> k;
	unordered_map<int,int>mp;
	ll ans=0;
	mp[0]++;
	for(int i=1;i<=n;i++){
		if(i>=k) mp[(a[i-k]-i+k)%k]--;
		cin >> a[i];
		a[i]+=a[i-1];
		ans+=mp[(a[i]-i)%k]++;
	}
	cout << ans;
}

星期三:

补 ABC 248 F                                                          atc传送门

思路:dp【i】【j】【0/1】表示考虑到第 i列删去了 j条边图是否联通的方案数

详细思路见:abc 248 F 题解 - 洛谷专栏 (luogu.com.cn)

代码如下:

ll n;
int P;
ll dp[3030][3030][2];
void solve(){
	cin >> n >> P;
	dp[1][0][1]=1,dp[1][1][0]=1;
	for(int i=1;i<=n;i++){
		for(int j=0;j<n;j++){
			(dp[i+1][j+2][0]+=dp[i][j][1]*2)%=P;
			
			(dp[i+1][j+1][1]+=dp[i][j][1]*3)%=P;
			(dp[i+1][j][1]+=dp[i][j][1])%=P;
			
			(dp[i+1][j+1][0]+=dp[i][j][0])%=P;
			(dp[i+1][j][1]+=dp[i][j][0])%=P;
		}
	}
	for(int i=1;i<n;i++) cout << dp[n][i][1] << " ";
}

河南萌新联赛2024 一 D                                        牛客传送门

思路:按位计算贡献,找出规律,每两个数中有一个1,每四个数中有两个2,每八个数有四个4

代码如下:

const int mod=998244353;
ll n;
inline ll cal(ll x,int i){
	ll res=x/(1ll<<i+1)*(1ll<<i)%mod;
	x%=(1ll<<i+1);
	x-=(1ll<<i)-1;
	if(x>0){
		res+=x;
		if(x==(1ll<<i+1)) res--;
	}
	return res;
}
void solve(){
	int t; cin >> t;
	while(t--){
		ll l,r; cin >> l >> r;
		ll ans=0;
		for(int i=0;i<=60;i++) (ans+=cal(r,i)-cal(l-1,i))%=mod;
		cout << ans << "\n";
	}
}

B 朵拉爱探险                                                      牛客传送门

思路:和昨天的题有点相似,区别是此题没有排列的约束,所以并不全是闭环

路径有两种可能,一种是树形连通块,找出最长的一条路径即可,第二种为环形,但并不只是环,可能还有链依附在上面,需要求出环的长度及其最长的一条链

先拓扑排序判环,链状图的最长路径用dfs求即可,环状图可存入map里,对链上点dfs求最长链并统计环上点个数,即环的长度

代码如下:

const int N=2e6+10,M=210;
ll n;
int a[N],in[N],fa[N];
int tin[N];
vector<int>fr[N];
bool tr[N],d[N];
inline int fnd(int x){
	return fa[x]==x?x:fa[x]=fnd(fa[x]);
}
void tp(){
	queue<int>qu;
	for(int i=1;i<=n;i++)
		if(!tin[i]) qu.push(i),tr[i]=1;
	while(!qu.empty()){
		int t=qu.front(); qu.pop();
		int v=a[t];
		tin[v]--;
		if(!tin[v]) qu.push(v),tr[v]=1;    //tr[i]代表i点是否在链上
	}
}
int len[N];
inline int dfs(int x){
	if(!in[x]) return 1;
	len[x]=1;
	int ma=0;
	for(int f:fr[x]){
		if(len[f]) ma=max(len[f],ma);
		else ma=max(dfs(f),ma);
	}
	len[x]+=ma;
	return len[x];
}
void solve(){
	cin >> n;
	for(int i=1;i<=n;i++) fa[i]=i;
	for(int i=1;i<=n;i++){
		cin >> a[i];
		in[a[i]]++,tin[a[i]]++;
		fr[a[i]].push_back(i);
		int u=fnd(i),v=fnd(a[i]);
		if(u!=v) fa[u]=v;
	}
	tp();
	for(int i=1;i<=n;i++) if(!tr[i]) d[fnd(i)]=1;    //此连通块有环
	int ans=0;
	map<int,vector<int>>mp;
	for(int i=1;i<=n;i++){
		if(!d[fnd(i)]) ans=max(dfs(i),ans);  //无环,选择链上最长路径
		else mp[fnd(i)].push_back(i);        //有环,先把连通块存入map里
	}
	for(auto [f,ve]:mp){
		int huan=0,lian=0;
		for(int j:ve){
			if(!tr[j]) huan++;              //在环上,环长度++
			else lian=max(dfs(j),lian);     //在链上,选取最长链
		}
		ans=max(huan+lian,ans);
	}
	cout << ans;
}

补河萌 2024 一 J                                                     牛客传送门

思路:这个箭头方向不好操作,将矩阵上下翻转,就变成了找指左上方的箭头,可以动态维护

处理出二维前缀和,如果dp【i-1】【j-1】是一个箭头尾,那么i,j接在后面的条件是三个点为1,其余点为0,满足则dp【i】【j】= dp【i-1】【j-1】+1

还有种是dp【i】【j】= dp【i-1】【j-1】,但目前没想到这种转移的情况长什么样

代码如下:

ll n;
char c[5050][5050];
int sum[5050][5050],dp[5050][5050];
int get(int x1, int y1, int x2, int y2)
{
	return sum[x2][y2] - sum[x1 - 1][y2] - sum[x2][y1 - 1] + sum[x1 - 1][y1 - 1];
}
void solve(){
	int m; cin >> n >> m;
	for(int i=n;i;i--)
		for(int j=1;j<=m;j++)
			cin >> c[i][j];
	for(int i=1;i<=n;i++)
		for(int j=1;j<=m;j++)
			sum[i][j]=sum[i][j-1]+sum[i-1][j]-sum[i-1][j-1]+(c[i][j]=='1');
	int ans=0;
	for(int i=1;i<=n;i++){
		for(int j=1;j<=m;j++){
			if(c[i][j]=='1') dp[i][j]=1,ans=max(dp[i][j],ans);
			if(!dp[i-1][j-1] || c[i][j]!='1') continue;
			int k=dp[i-1][j-1];
			bool if1=1,if0=1;
			if(c[i][j-k]!='1' || c[i-k][j]!='1') if1=0;
			if(sum[i][j]-sum[i-k][j]-sum[i][j-1]+sum[i-k][j-1]-1) if1=0;
			if(sum[i][j]-sum[i][j-k]-sum[i-1][j]+sum[i-1][j-k]-1) if1=0;
			if(if1) dp[i][j]=dp[i-1][j-1]+1;
			else{
				if(c[i-k+1][j]!='1' || c[i][j-k+1]!='1') if0=0;
				if(get(i-k+1,j-k+1,i,j)!=3*k-2) if0=0;
				if(if0) dp[i][j]=dp[i-1][j-1];
			}
			ans=max(dp[i][j],ans);
		}
	}
	cout << ans;
}

星期四:

洛谷 tarjan板子题                                                    洛谷传送门

思路:tarjan求强连通分量就完事了

D14 强连通分量 Tarjan 算法_哔哩哔哩_bilibili

代码如下:

const int N=2e6+10,M=210;
ll n;
int dfn[N],low[N],tot;
stack<int>sk;
bool ins[N];
int scc[N],sz[N],cnt;
vector<int>ve[N];
void tarjan(int x){
	dfn[x]=low[x]=++tot;
	sk.push(x); ins[x]=1;
	for(int v:ve[x]){
		if(!dfn[v]){
			tarjan(v);
			low[x]=min(low[v],low[x]);
		}else if(ins[v]) low[x]=min(dfn[v],low[x]);
	}
	if(dfn[x]==low[x]){
		int y; cnt++;
		do{
			y=sk.top();
			ins[sk.top()]=0; sk.pop();
			scc[y]=cnt;
			sz[cnt]++;
		}while(y!=x);
	}
}
void solve(){
	int m; cin >> n >> m;
	for(int i=1;i<=m;i++){
		int u,v; cin >> u >> v;
		ve[u].push_back(v);
	}
	for(int i=1;i<=n;i++) if(!dfn[i]) tarjan(i);
	ll ans=0;
	for(int i=1;i<=cnt;i++) ans+=(sz[i]>1);
	cout << ans;
}

牛客暑假多校2024 二 E                                        牛客传送门

题意:选一个严格小于 x的 y使 gcd (x,y) == x^y

思路:开始想到若 x为奇,y可为x-1,然后忘记了y需严格小于x,交了个若x为偶则y为x+1

想到若 x为偶且第二位为1,可构造 y为 x-2,到这可猜出结论 y为 x减去二进制最低位1

代码如下:

void solve(){
	ll x; cin >> x;
	ll y=0;
	for(int i=0;i<=60;i++)
		if(x&(1ll<<i)){y=x-(1ll<<i); break;}   //也可写为y=x-(x&-x)
	if(!y) cout << "-1\n";
	else cout << y << "\n";
}

H                                                                      牛客传送门

题意:给一操作字符串,问其子串数,子串满足执行路径经过 (x,y)点

思路:处理出前缀和是第一步。开始想过线段树二分,但无法处理tag及合并操作,故作罢。

从 i点开始走,找到第一个到达目标点的 j,可以得知对于纵向和横向步数的要求,加上 i-1前缀和造成的偏移,得到目标坐标 tx和 ty,可二分搜索。把所有到达的坐标存入multiset即可实现

代码如下:

const int N=2e6+10,M=210;
ll n;
int su[N],sd[N],sl[N],sr[N];
struct nod{
	int ud,rl;                    //上下和右左的差值
	int id;
	bool operator <(const nod &b)const{  //重写比较,满足二分要求
		if(ud!=b.ud) return ud<b.ud;
		if(rl!=b.rl) return rl<b.rl;
		return id<b.id;
	}
};
void solve(){
	int x,y; cin >> n >> y >> x;      //后面的代码里把x处理为行,y处理为列,所以输入反过来
	string s; cin >> s; s=" "+s;
	if(!x && !y){cout << (n+1)*n/2; return ;}   //特判原点
	multiset<nod>mt;
	for(int i=1;i<=n;i++){
		su[i]=su[i-1]+(s[i]=='W');
		sd[i]=sd[i-1]+(s[i]=='S');
		sl[i]=sl[i-1]+(s[i]=='A');
		sr[i]=sr[i-1]+(s[i]=='D');
		mt.insert({su[i]-sd[i],sr[i]-sl[i],i});
	}
	ll ans=0;
	for(int i=1;i<=n;i++){
		int tx=x+su[i-1]-sd[i-1];
		int ty=y+sr[i-1]-sl[i-1];
		if(i>1) mt.erase(mt.find({su[i-1]-sd[i-1],sr[i-1]-sl[i-1],i-1}));
		auto it=mt.lower_bound({tx,ty,0});
		if(it==mt.end()) continue;
		nod j=*it;
		if(j.ud!=tx || j.rl!=ty) continue;
		ans+=n-j.id+1;                       //起点为i,终点j-n的路径都合法
	}
	cout << ans;
}

洛谷 P2812 tarjan缩点                                           洛谷传送门

思路:tarjan先处理出强联通分量,把一个分量视作一个点,计算每个点的出度入度,入度为0的点即为母机。 第二问为添加最少边使其成环,答案为入度为0和出度为0的点的数量的 max,可推导也可画图验证

若强连通量只有一个,则无需添边而不是1,记得特判!!(wa了一发

代码如下:

const int N=2e6+10,M=210;
ll n;
int dfn[N],low[N],tot;
stack<int>sk; bool ins[N];
int ssc[N],sz[N],cnt;
vector<int>ve[N];
int in[N],out[N];
void tarjan(int x){
	dfn[x]=low[x]=++tot;
	sk.push(x); ins[x]=1;
	for(int v:ve[x]){
		if(!dfn[v]){
			tarjan(v);
			low[x]=min(low[v],low[x]);
		}else if(ins[v]) low[x]=min(dfn[v],low[x]);
	}
	if(dfn[x]==low[x]){
		int y; ++cnt;
		do{
			y=sk.top();
			ins[sk.top()]=0,sk.pop();
			ssc[y]=cnt;
			sz[cnt]++;
		}while(y!=x);
	}
}
void solve(){
	cin >> n;
	for(int i=1;i<=n;i++){
		int x;
		while(cin >> x && x) ve[i].push_back(x);
	}
	for(int i=1;i<=n;i++) if(!dfn[i]) tarjan(i);
	for(int x=1;x<=n;x++){
		for(int v:ve[x]){
			if(ssc[x]!=ssc[v]){
				in[ssc[v]]++;
				out[ssc[x]]++;         //处理缩点后的入度和出度
			}
		}
	}
	int a=0,b=0;
	for(int i=1;i<=cnt;i++)
		a+=!in[i],b+=!out[i];
	cout << a << "\n";
	if(cnt==1) cout << 0;          //关键的特判
	else cout << max(a,b);
}

洛谷 P2341                                                     洛谷传送门

思路:和上题大体很像,求明星牛所在分量时需注意,条件并不是入度为cnt-1,而是出度为0,  出度为0这条件乍一看有点奇怪,实际上是对的,而入度为cnt-1因为爱慕可传递,所以是错的

还需注意出度为0的分量只能有一个,大于一个则无明星牛

代码如下:

const int N=2e6+10,M=210;
ll n;
int dfn[N],low[N],tot;
stack<int>sk; bool ins[N];
int ssc[N],sz[N],cnt;
vector<int>ve[N];
int in[N],out[N];
void tarjan(int x){
	dfn[x]=low[x]=++tot;
	sk.push(x); ins[x]=1;
	for(int v:ve[x]){
		if(!dfn[v]){
			tarjan(v);
			low[x]=min(low[v],low[x]);
		}else if(ins[v]) low[x]=min(dfn[v],low[x]);
	}
	if(dfn[x]==low[x]){
		int y; ++cnt;
		do{
			y=sk.top();
			ins[sk.top()]=0,sk.pop();
			ssc[y]=cnt;
			sz[cnt]++;
		}while(y!=x);
	}
}
void solve(){
	int m; cin >> n >> m;
	for(int i=1;i<=m;i++){
		int a,b; cin >> a >> b;
		ve[a].push_back(b);
	}
	for(int i=1;i<=n;i++) if(!dfn[i]) tarjan(i);
	if(cnt==1){cout << n; return ;}
	for(int x=1;x<=n;x++){
		for(int v:ve[x])
			if(ssc[x]!=ssc[v]){
				in[ssc[v]]++;
				out[ssc[x]]++;
			}
	}
	int ans=0;
	for(int i=1;i<=cnt;i++){
		if(!out[i]){
			if(!ans) ans=sz[i];
			else{ans=0; break;}
		}
	}
	cout << ans;
}

洛谷 P3387                                                        洛谷传送门

思路:缩点后在DAG上跑dfs秒了,dfs参照朵拉求链长

代码如下:

const int N=1e4+10,M=210;
ll n;
int a[N],sum[N];
int dfn[N],low[N],tot;
stack<int>sk; bool ins[N];
int ssc[N],sz[N],cnt;
vector<int>ve[N];
int in[N],out[N];
set<int>fr[N];
void tarjan(int x){
	dfn[x]=low[x]=++tot;
	sk.push(x); ins[x]=1;
	for(int v:ve[x]){
		if(!dfn[v]){
			tarjan(v);
			low[x]=min(low[v],low[x]);
		}else if(ins[v]) low[x]=min(dfn[v],low[x]);
	}
	if(dfn[x]==low[x]){
		int y; ++cnt;
		do{
			y=sk.top();
			ins[sk.top()]=0,sk.pop();
			ssc[y]=cnt;
			sz[cnt]+=a[y];
		}while(y!=x);
	}
}
int dfs(int x){
	if(!in[x]) return sz[x];
	int res=sz[x],ma=0;
	for(int f:fr[x]){
		if(sum[f]) ma=max(sum[f],ma);
		else ma=max(dfs(f),ma);
	}
	res+=ma;
	return sum[x]=res;
}
void solve(){
	int m; cin >> n >> m;
	for(int i=1;i<=n;i++) cin >> a[i];
	for(int i=1;i<=m;i++){
		int a,b; cin >> a >> b;
		ve[a].push_back(b);
	}
	for(int i=1;i<=n;i++) if(!dfn[i]) tarjan(i);
	for(int x=1;x<=n;x++){
		for(int v:ve[x])
			if(ssc[x]!=ssc[v]){
				in[ssc[v]]++;
				out[ssc[x]]++;
				fr[ssc[v]].insert(ssc[x]);
			}
	}
	int ans=0;
	for(int i=1;i<=cnt;i++) if(!sum[i]) ans=max(dfs(i),ans);
	cout << ans;
}

星期五:

上午个人赛 签了个到

补 ABC 211 E                                                     atc传送门

思路:状压解决不了此题,需暴搜,数据范围小且是连通块,时间可接受

使用vector<string>和set容器来判重

代码如下:

ll n;
vector<string>s;
set<vector<string>>st;
ll ans;
int dx[]={-1,1,0,0};
int dy[]={0,0,-1,1};
void dfs(int lef){
	if(st.find(s)!=st.end()) return ;    //搜过此图
	st.insert(s);
	if(!lef){ans++; return ;}
	vector<PII>ve;                       //接下来可以涂红的点
	for(int i=0;i<n;i++){
		for(int j=0;j<n;j++)
			if(s[i][j]=='.'){
				for(int k=0;k<4;k++){
					int xx=i+dx[k],yy=j+dy[k];
					if(xx<0 || xx>=n || yy<0 || yy>=n) continue;
					if(s[xx][yy]=='r'){
						ve.push_back({i,j});
						break;
					}
				}
			}
	}
	for(auto [x,y]:ve){
		s[x][y]='r';
		dfs(lef-1);
		s[x][y]='.';    //回溯
	}
}
void solve(){
	int k; cin >> n >> k;
	s.resize(n);
	for(int i=0;i<n;i++) cin >> s[i];
	for(int i=0;i<n;i++){
		for(int j=0;j<n;j++){
			if(s[i][j]=='.'){
				s[i][j]='r';
				dfs(k-1);
				s[i][j]='.';    //回溯
			}
		}
	}
	cout << ans;
}

补 ABC 230 F                                                   atc传送门

思路:乍一看题意很刁,仔细分析下合并操作,合并并不会影响总和即sum【n】,但如果合并第 i和 i+1的元素,会使得sum【i】消失,而已知前缀和数组,可得到原数组,于是题意可转化为在前缀和数组中有多少个不同的子序列,sum【n】必选

dp【i】表示 1-i的元素中有多少不同子序列,mp【i】表示 i值上次出现的位置

若 !mp【sum【i】】,dp【i】= dp【i-1】* 2 + 1  表示前 i-1的子序列数以及末尾加上sum【i】若 mp【sum【i】】= lst,dp【i】= dp【i-1】* 2 - dp【lst-1】,因为在 dp【lst-1】的序列末尾加上 sum【i】得到的序列数已经被计算过了

代码如下:

const int N=2e6+10,M=210;
const int mod=998244353;
ll n;
ll a[N];
ll dp[N];
void solve(){
	cin >> n;
	for(int i=1;i<=n;i++){
		cin >> a[i];
		a[i]+=a[i-1];
	}
	map<ll,int>mp;
	for(int i=1;i<n;i++){
		if(!mp[a[i]]) dp[i]=(dp[i-1]*2+1)%mod;
		else dp[i]=(dp[i-1]*2-dp[mp[a[i]]-1]+mod)%mod;  //减法记得+mod后再模
		mp[a[i]]=i;
	}
	cout << (dp[n-1]+1)%mod;    //算上空序列
}

星期六:

上午友谊赛2 退步一名

补 cf round629 div3 E                                            cf传送门

思路:因为建树没建双向边被拿下了

!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

!!!!!!!!!!!!!!!!!!!!警钟长鸣啊!!!!!!!!!!!!!!!!!!

!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

代码如下:

const int N=2e6+10,M=210;
ll n;
vector<int>ve[N];
int dep[N],fa[N][20]; //1<<20 近似 1e6
void dfs(int u,int f){                     //O(nlogn)
	dep[u]=dep[f]+1;
	fa[u][0]=f;
	for(int i=1;i<=19;i++)
		fa[u][i]=fa[fa[u][i-1]][i-1];
	for(int v:ve[u])
		if(v!=f) dfs(v,u);
}
int lca(int u,int v){                      //O(logn)
	if(dep[u]<dep[v]) swap(u,v);
	for(int i=19;i>=0;i--)
		if(dep[fa[u][i]]>=dep[v]) u=fa[u][i];
	if(u==v) return v;
	for(int i=19;i>=0;i--)
		if(fa[u][i]!=fa[v][i])
			u=fa[u][i],v=fa[v][i];
	return fa[u][0];
}
void solve(){
	int q; cin >> n >> q;
	for(int i=1;i<n;i++){
		int u,v; cin >> u >> v;
		ve[u].push_back(v);
		ve[v].push_back(u);     //建双向边建双向边建双向边建双向边建双向边建双向边
	}
	dfs(1,0);
	while(q--){
		int k; cin >> k;
		int to=1;
		bool if1=1;
		while(k--){
			int p; cin >> p;
			if(dep[p]<=1) continue;
			int an=lca(to,p);
			if(dep[p]<=dep[to]){
				if(p!=an && fa[p][0]!=an) if1=0;
			}else{
				if(an!=to) if1=0;
				else to=fa[p][0];
			}
		}
		if(if1) cout << "YES\n";
		else cout << "NO\n";
	}
}

牛客树形dp题单 D                                                牛客传送门

题意:丧尸舞会升级版

思路:dp【x】【0/1/2】表示 x点被 自己/儿子/父亲 覆盖所需代价

若被儿子覆盖,儿子中至少有一个 0就行,转移时记录下儿子 0和1的差值,取最小的补上就行

代码如下:

const int N=2e5+10,M=210;
ll n;
vector<int>ve[N];
int dp[N][3];
void dfs(int x,int f){
	dp[x][0]=1;
	dp[x][1]=dp[x][2]=0;
	int mi=1e9;
	for(int v:ve[x]){
		if(v==f) continue;
		dfs(v,x);
		dp[x][0]+=min({dp[v][0],dp[v][1],dp[v][2]});
		dp[x][2]+=min(dp[v][0],dp[v][1]);
		dp[x][1]+=min(dp[v][0],dp[v][1]);
		mi=min(dp[v][0]-dp[v][1],mi);
	}
	if(mi>0) dp[x][1]+=mi;
	dp[x][1]=max(1,dp[x][1]);         //如果没儿子,还是得靠自己
}
void solve(){
	cin >> n;
	for(int i=1;i<n;i++){
		int u,v; cin >> u >> v;
		ve[u].push_back(v);
		ve[v].push_back(u);
	}
	dfs(1,0);
	cout << min(dp[1][0],dp[1][1]);
}

周日:

补 ABC 216 F                                                         atc传送门

思路:dp【i】【j】表示考虑到 i时,选取 b和为 j的方案数

先将 a和b按a从小到大排序,这样遍历时 a【i】就是 a中最大值,b【i】必选,那么需要知道在前  i-1个 b中选取使得和 <= a【i】- b【i】的方案数,其实就是背包dp的写法

代码如下:

const int mod=998244353;
ll n;
PII a[5050];
ll dp[5050][5050],sum[5050];
void solve(){
	cin >> n;
	int ma=0;
	for(int i=1;i<=n;i++){
		cin >> a[i].first;
		ma=max(a[i].first,1ll*ma);
	}
	for(int i=1;i<=n;i++) cin >> a[i].second;
	sort(a+1,a+n+1);
	sum[0]=1;
	for(int i=1;i<=n;i++){
//		for(int j=1;j<=ma;j++){
//			dp[i][j]=dp[i-1][j];
//			if(a[i].second<=j) (dp[i][j]+=dp[i-1][j-a[i].second])%=mod;
//			if(j<=a[i].first) (ans+=dp[i][j])%=mod;
//		}
		for(int j=a[i].second;j<=ma;j++) dp[i][j]=sum[j-a[i].second];
		for(int j=a[i].second;j<=ma;j++) (sum[j]+=dp[i][j])%=mod;
	}
	ll ans=0;
	for(int i=1;i<=n;i++)
		for(int j=0;j<=a[i].first;j++) (ans+=dp[i][j])%=mod;
	cout << ans;
}

一维优化(dp【j】表示选取 b组成 j的方案数 :

const int mod=998244353;
ll n;
PII a[5050];
ll dp[5050];
void solve(){
	cin >> n;
	for(int i=1;i<=n;i++) cin >> a[i].first;
	for(int i=1;i<=n;i++) cin >> a[i].second;
	sort(a+1,a+n+1);
	ll ans=0;
	dp[0]=1;
	for(int i=1;i<=n;i++){
		for(int j=0;j<=a[i].first-a[i].second;j++) (ans+=dp[j])%=mod;
		for(int j=5000;j>=a[i].second;j--) (dp[j]+=dp[j-a[i].second])%=mod;
	}
	cout << ans;
}

  • 17
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值