24.3.17周报(倍增/tarjan LCA,数位dp,ST表(RMO) )

星期一:

继续补天梯选拔赛round 2的题:

思路:线性dp加前缀和,dp[ i , j ]表示考虑到前i个中挑k组的最大获利, 最终答案为dp[n,k] , 两重循环跑n和k

代码如下:

ll n;
int m,k;
ll dp[5050][5050],p[5050];
void solve(){
	cin >> n >> m >> k;
	for(int i=1;i<=n;i++){
		cin >> p[i];
		p[i]+=p[i-1];
	}
	memset(dp,-1,sizeof dp);
	for(int i=0;i<=n;i++) dp[i][0]=0;
	for(int i=m;i<=n;i++){
		for(int j=1;j<=k;j++){
			dp[i][j]=dp[i-1][j];
			if(dp[i-m][j-1]==-1) continue;
			dp[i][j]=max(dp[i-m][j-1]+p[i]-p[i-m],dp[i][j]);
		}
	}
	cout << dp[n][k] << "\n";
	stack<int>ans;
	int cnt=k;
	for(int i=n;i>=m && cnt;i--){
		if(dp[i][cnt]==dp[i-m][cnt-1]+p[i]-p[i-m]){
			ans.push(i);
			i-=m-1,cnt--;
		}
	}
	while(!ans.empty()){
		int tmp=ans.top(); ans.pop();
		cout << tmp-m+1 << " " << tmp << "\n";
	}
}

晚上cf round933 div3,做到了E,优先队列优化线性dp,能赛时做出来我很满意

F题思路比E更简单,没做出来因为时间不够,赛后补了

星期二:

pta上天梯训练赛,手速可以,败在上限

补一题:

为了这题赛时学了个最近公共祖先lca(least common ancestor),结果题解发现用不着,汗流浃背了

思路:每个点往上dfs,d到标记过的点就结束,因为默认返回,所以return 路径*2,记录从外卖站开始的一条最长的路径不返回,所以输出要减一个最长路径,感觉这题要点手法的

代码如下:

const int N=1e6+10;
const int mod=1e9+7;
ll n;
int m,s,ma;
int fa[N],a[N];
bool vi[N];
ll ans;
int dfs(int x,int cnt){
	if(x==s || a[x]){
		ma=max(a[x]+cnt,ma);
		return cnt*2;
	}
	int res=dfs(fa[x],cnt+1);
	a[x]=a[fa[x]]+1;
	return res;
}
void solve(){
	cin >> n >> m;
	for(int i=1;i<=n;i++){
		int x;cin >> x;
		if(x==-1){s=i; continue;}
		fa[i]=x;
	}
	vi[s]=1;
	while(m--){
		int x;cin >> x;
		ans+=dfs(x,0);
		cout << ans-ma << "\n";
	}
}

贴个最近公共祖先LCA板子:     时间复杂度 O( (n+m) logn )

const int N=1e6+10;
const int mod=1e9+7;
ll n;
int m;
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];
}

星期三:

补cf round933 G题:

题意:无向图,无自环,无重边,两点间的边以一种颜色标注,给定起始点和终点,输出路径最少经过的颜色数

思路:学到了个新东西,建立虚点,代替原来的边,将图进行转化,每种颜色建一个虚点,连接此颜色边所连的点,虚点到点的单向边边权为0,点到虚点单向边边权为1,这样同颜色边所连接的点就能以 1 的代价移动,对应题意

如例题图,建立虚点后的图是这样的:

图建好后跑个dij就行(01bfs也可以)

代码如下:

const int N=1e6+10;
const int mod=1e9+7;
ll n;
int m,b,e;
vector<PII>ve[N];
ll dis[N];
bool vi[N];
void dij(int s){
	priority_queue<PII,vector<PII>,greater<PII>>pq;
	for(int i=1;i<=n+m+2;i++) dis[i]=1e18;
	dis[s]=0;
	pq.push({0,s});
	while(pq.size()){
		int t=pq.top().second,d=pq.top().first; pq.pop();
		if(vi[t]) continue;
		vi[t]=1;
		for(auto [x,y]:ve[t]){
			if(d+y<dis[x]){
				dis[x]=d+y;
				if(x==e) return ;
				pq.push({dis[x],x});
			}
		}
	}
}
void solve(){
	cin >> n >> m;
	map<int,int>mp;
	int sp=1;
	for(int i=1;i<=m;i++){
		int u,v,c;
		cin >> u >> v >> c;
		if(mp[c]==0) mp[c]=sp++;
		ve[n+mp[c]].push_back({u,0});
		ve[n+mp[c]].push_back({v,0});
		ve[u].push_back({n+mp[c],1});
		ve[v].push_back({n+mp[c],1});
	}
	cin >> b >> e;
	dij(b);
	cout << dis[e] << "\n";
	for(int i=1;i<=n+m+2;i++) ve[i].clear(),vi[i]=0;
}

  入门 数位dp          b站传送门

代码如下:

ll n;
int f[12][10],a[12];
void init(){
	for(int i=0;i<=9;i++) f[1][i]=1;
	for(int i=1;i<=11;i++){           //可以涵盖9.9e10范围的数
		for(int j=0;j<=9;j++)
			for(int k=j;k<=9;k++)
				f[i][j]+=f[i-1][k];
	}
}
ll dp(ll n){
	if(!n) return 1;
	int cnt=0,now=0,last=0;
	ll res=0;
	while(n) a[++cnt]=n%10,n/=10;
	for(int i=cnt;i>=1;i--){
		now=a[i];
		for(int j=last;j<now;j++)
			res+=f[i][j];
		if(now<last) break;
		last=now;
		if(i==1) res++;
	}
	return res;
}
void solve(){
	init();
	int x,y;
	while(cin >> x >> y){
		cout << dp(y)-dp(x-1) << endl;
	}
}

做题做到个询问区间最大值,死活想不出来,原来是又得学新算法了

ST表(RMO)板子:        O(nlogn)初始化,O(1)查询

const int N=2e6+10;
const int mod=1e9+7;
ll n;
int t,q;
int a[N];
ll f[N][20];
void init(){
	for(int i=1;i<=n;i++) f[i][0]=a[i];
	for(int j=1;j<20;j++)
		for(int i=1;i+(1<<j)-1<=n;i++)
			f[i][j]=max(f[i][j-1],f[i+(1<<j-1)][j-1]);
}
ll que(int l,int r){
	int k=log2(r-l+1);
	return max(f[l][k],f[r-(1<<k)+1][k]);
}

星期四:

数位dp  windy数  洛谷传送门

思路:代码的形式和之前的不降数很像,先预处理,然后前缀和计算

代码如下:

ll n;
int a[12];
ll f[12][20];                                    //位数为i,最高位为j含有的windy数
void init(){
	for(int i=0;i<=9;i++) f[1][i]=1;
	for(int i=2;i<12;i++)
		for(int j=0;j<=9;j++)
			for(int k=0;k<=9;k++)
				if(abs(j-k)>=2) f[i][j]+=f[i-1][k];
}
ll dp(int x){
	if(!x) return 0;
	int cnt=0,last=-2,now=0;ll res=0;
	while(x) a[++cnt]=x%10,x/=10;
	for(int i=cnt;i>=1;i--){                     //位数为cnt的数对答案的贡献
		now=a[i];
		for(int j=(i==cnt);j<now;j++)            //i==cnt时,j从1开始
			if(abs(j-last)>=2) res+=f[i][j];
		if(abs(now-last)<2) break;
		last=now;
		if(i==1) res++;
	}
	for(int i=1;i<cnt;i++)                       //位数小于cnt的数对答案的贡献
		for(int j=1;j<=9;j++) res+=f[i][j];      //位数小于cnt时最高位不能为0
	return res;
}
void solve(){
	init();
	int x,y;cin >> x >> y;
	cout << dp(y)-dp(x-1);
}

数位dp   度的数量    b站传送门

思路: 用数组模拟b进制,满足条件的数即为恰好有k个数位为1的数,需预处理组合数

代码如下:

ll n;
int k,b;
int a[33];
int f[33][33];
void init(){
	for(int i=0;i<33;i++) f[i][0]=1;
	for(int i=1;i<33;i++)
		for(int j=1;j<=i;j++)
			f[i][j]=f[i-1][j]+f[i-1][j-1];
}
ll dp(int x){
	if(!x) return 0;
	int cnt=0,last=0;ll res=0;
	while(x) a[++cnt]=x%b,x/=b;
	for(int i=cnt;i>=1;i--){
		if(a[i]){
			res+=f[i-1][k-last];
			if(a[i]==1){
				last++;
				if(last>k) break;
			}else{
				if(k-last-1>=0) res+=f[i-1][k-last-1];
				break;
			}
		}
		if(i==1 && last==k) res++;
	}
	return res;
}
void solve(){
	init();
	int x,y; cin >> x >> y;
	cin >> k >> b;
	cout << dp(y)-dp(x-1);
}

数位dp       洛谷传送门

经过三道数位dp入门后,自己在洛谷上找的一道,攻克失败

先看初始化,f [ i , j ]还是表示位数为i,最高位为j的所有数的数字和

我自己写的初始化函数:

ll f[20][10],a[20];
void init(){
	for(int i=0;i<=9;i++) f[1][i]=i;
	for(int i=2;i<32;i++)
		for(int j=0;j<=9;j++)
			for(int k=0;k<=9;k++)
				f[i][j]+=f[i-1][k]+j,f[i][j]%=mod;
}

错在哪呢,f[3,1]应该是1000,我这函数初始化后只有910,原因在于只加了十次百位的1,而正确的应该加上从00到99一百次1,正确初始化函数如下:

ll f[20][10],a[20],p10[20];
void init(){
	p10[0]=1;       //意为pow(10,i)
	for(int i=1;i<20;i++) p10[i]=p10[i-1]*10,p10[i]%=mod;
	for(int i=0;i<=9;i++) f[1][i]=i;
	for(int i=2;i<20;i++){
		for(int j=0;j<=9;j++){
			f[i][j]=j*p10[i-1];
			for(int k=0;k<=9;k++)
				f[i][j]+=f[i-1][k],f[i][j]%=mod;
		}
	}
}

星期五:

经过上午及一点点中午的攻克,上题已成功拿下

放代码:

ll n;
ll f[20][10],a[20];
unsigned long long p10[20];             //不用ull数据范围也够
void init(){
	p10[0]=1;
	for(int i=1;i<20;i++) p10[i]=p10[i-1]*10;   //这里不模mod,否则下面的取模操作会出错
	for(int i=0;i<=9;i++) f[1][i]=i;
	for(int i=2;i<20;i++){
		for(int j=0;j<=9;j++){
			f[i][j]=j*p10[i-1],f[i][j]%=mod;
			for(int k=0;k<=9;k++)
				f[i][j]+=f[i-1][k],f[i][j]%=mod;
		}
	}
}
ll dp(ll x){             //这里dp不能用无符号型,因为下面的相减操作可能出现负数
	if(!x) return 0;
	int cnt=0,now=0;ll res=0,tmp=x;
	while(x) a[++cnt]=x%10,x/=10;
	for(int i=cnt;i>=1;i--){         //位数等于cnt的数对res的贡献
		now=a[i];
		for(int j=(i==cnt);j<now;j++)               //i为cnt时,j从1开始,后面可从0开始
			res+=f[i][j],res%=mod;
		res+=now*(tmp%p10[i-1]+1)%mod,res%=mod;         //固定高位,加上高位数对res的贡献
	}
	for(int i=1;i<cnt;i++)           //位数小于cnt的数对res的贡献
		for(int j=1;j<=9;j++)
			res+=f[i][j],res%=mod;
	return res;
}
void solve(){
	init();
	int t; cin >> t;
	while(t--){
		ll l,r; cin >> l >> r;
		cout << ((dp(r)-dp(l-1))%mod+mod)%mod << "\n";  //模后相减可能为负,需再+mod)%mod处理
	}
}

之前卡我的cf round928的C也可以酱紫秒了,虽然其正解是前缀和

星期六:

成信大二次游,打了个他们的天梯校赛,补题链接也不给个

晚上cf round934 div2又掉分,连续两晚咔咔掉

C题开始贪心思路错了,后面又漏了个break,整半天,罚时寄完了

周日:

简单vp下昨晚的abc 345,C题也是思路没完善导致wa了四发,止步于D

补了下arc173的A,也算数位dp:                    ATC传送门

思路:应该有其他思路,但我是当数位dp加二分做的,二分的时候注意dp结果等于k了也不一定是答案,可能是大于答案的值,所以记录一下再往下分

有一个比较坑的点就是它没说第1e12个neq数大概会是多大,所以二分的上界不好确定,之前用ll的1e18就wa了很多发,得用ull的1e19才行

代码如下:

ll n;
ll f[20][10],a[20];
void init(){
	for(int i=0;i<=9;i++) f[1][i]=1;
	for(int i=2;i<20;i++){
		for(int j=0;j<=9;j++)
			for(int k=0;k<=9;k++)
				if(j!=k) f[i][j]+=f[i-1][k];
	}
}
ll dp(ll x){
	int cnt=0;ll res=0;
	while(x) a[++cnt]=x%10,x/=10;
	int last=0;
	for(int i=cnt;i;i--){
		int now=a[i];
		for(int j=(i==cnt);j<now;j++)
			if(j!=last) res+=f[i][j];
		if(now==last) break;
		last=now;
		if(i==1) res++;
	}
	for(int i=1;i<cnt;i++)
		for(int j=1;j<=9;j++)
			res+=f[i][j];
	return res;
}
void solve(){
	init();
	int t; cin >> t;
	while(t--){
		ll k; cin >> k;
		if(k<=10){cout << k << "\n"; continue;}
		ll l=12,r=1e19,ans=0;
		while(l<=r){
			ll mid=l+(r-l)/2;
			ll res=dp(mid);
			if(res>k) r=mid-1;
			else if(res<k) l=mid+1;
			else ans=mid,r=mid-1;        //记录后继续往下分
		}
		cout << ans << "\n";
	}
}

尝试补了下ABC345的D,dfs暴搜,发现自己的dfs暴搜很弱,然后去练了下基础的八皇后问题

贴下代码:

ll n;
ll ans;
int a[20],c[20],p[40],q[40];
void print(){
	if(ans>3) return ;
	for(int i=1;i<=n;i++) cout << a[i] << " \n"[i==n];
}
void dfs(int i){
	if(i>n){ans++; print(); return ;}
	for(int j=1;j<=n;j++){
		if(c[j] || p[i+j] || q[i-j+n]) continue;
		c[j]=p[i+j]=q[i-j+n]=1;         //标记列和俩对角线
		a[i]=j;                         //填入
		dfs(i+1);
		c[j]=p[i+j]=q[i-j+n]=0;
	}
}
void solve(){
	cin >> n;
	dfs(1);
	cout << ans;
}

最近公共祖先LCA tarjan算法:                 复杂度O(n+m)

const int N=2e6+10;
const int mod=1e9+7;
ll n;
int m,s;
vector<int>e[N];
vector<PII>que[N];
int fa[N],ans[N];
bool vi[N];
int fnd(int x){
	return fa[x]==x?x:fa[x]=fnd(fa[x]);
}
void tarjan(int u){
	vi[u]=1;
	for(auto v:e[u]){
		if(vi[v]) continue;
		tarjan(v);                  //深搜子节点
		fa[v]=u;                    //出来后连上关系
	}
	for(auto [v,i]:que[u]){         //离开前遍历询问
		if(vi[v]) ans[i]=fnd(v);
	}
}
void solve(){
	cin >> n >> m >> s;
	for(int i=1;i<=n;i++) fa[i]=i;
	for(int i=1;i<n;i++){
		int u,v; cin >> u >> v;
		e[u].push_back(v);
		e[v].push_back(u);
	}
	for(int i=1;i<=m;i++){
		int a,b; cin >> a >> b;
		que[a].push_back({b,i});
		que[b].push_back({a,i});
	}
	tarjan(s);
	for(int i=1;i<=m;i++) cout << ans[i] << "\n";
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值