24.7.28(tarjan 割点,割边,多重背包单调队列优化)

星期一:

cf round 960 div2 B 简单构造                                   cf传送门

题意有点绕

思路:开始容易想到 y前和 x后全-1,y到x填1的构造,但对于 5 2 1,1 1 -1 -1 -1有问题,1和5的后缀值都为 -1,y判定为5

实际两边不需要全填-1,首先明确x和y的位置必填1,且y-1和x+1必填-1,此后-1和1交替填就行,若头尾是1,会出现错误吗,事实上是不会的,因为x严格大于y,故x至y的和即前后缀最大值>=2若头尾是-1,前后缀最大值>=1

代码如下:

const int N=2e6+10,M=210;
ll n;
int a[N];
void solve(){
	int x,y; cin >> n >> x >> y;
	a[y]=a[x]=1;
	for(int i=y-1;i;i--) a[i]=-a[i+1];
	for(int i=x+1;i<=n;i++) a[i]=-a[i-1];
	for(int i=y;i<=x;i++) a[i]=1;
	for(int i=1;i<=n;i++) cout << a[i] << " \n"[i==n];
}

贴 ABC322 F                                                      atc传送门

题意:询问01串中最长连续1的个数

思路:普通线段树题,贴上的原因是提醒下自己,merge时,如果存在懒标记,一定要初始化为0赛时忘了导致调试浪费了些许时间

代码如下:

    struct nod{
		int l,r;
		ll mal0,mal1,mar0,mar1,ma0,ma1;
		int tag;
	}t[N];
	ll ql,qr;
	nod merge(nod a,nod b){
		nod res;
		res.l=a.l,res.r=b.r;
		res.mal0=a.mal0==a.r-a.l+1?a.mal0+b.mal0:a.mal0;
		res.mal1=a.mal1==a.r-a.l+1?a.mal1+b.mal1:a.mal1;
		res.mar0=b.mar0==b.r-b.l+1?a.mar0+b.mar0:b.mar0;
		res.mar1=b.mar1==b.r-b.l+1?a.mar1+b.mar1:b.mar1;
		res.ma0=max({a.ma0,b.ma0,a.mar0+b.mal0});
		res.ma1=max({a.ma1,b.ma1,a.mar1+b.mal1});
		res.tag=0;                                   //注意懒标记初始化
		return res;
	}

贴 ABC044 C                                                  atc传送门

思路:和上周日补的题很像,都是背包dp

dp【i】【j】【k】表示考虑到第 i个,拿了 j个,总和为 k的方案数

代码如下(二维优化:

ll n;
int x[55],sum[55];
ll dp[55][2510];
void solve(){
	int a; cin >> n >> a;
	for(int i=1;i<=n;i++){
		cin >> x[i];
		sum[i]=sum[i-1]+x[i];
	}
	dp[0][0]=1;
	for(int i=1;i<=n;i++){
		for(int k=sum[i];k>=x[i];k--)  //因为优化了第一维,所以k无需枚举到1
			for(int j=i;j;j--) dp[j][k]+=dp[j-1][k-x[i]];
	}
	ll ans=0;
	for(int i=1;i<=n;i++) ans+=dp[i][i*a];
	cout << ans;
}

补 cf edu round124 D                                              cf传送门

题意:给n个点,对每个点给出一个曼哈顿距离最小且不在点集内的点

思路:刚开始以为要用啥不会的算法。。实则不然,我们可以很轻易先知道答案距离为1的点,而如果一点答案不为1,但挨着一个答案为1的点,那么可得此点答案为2,同理可得一点答案为3,到此可知 bfs处理即可

代码如下:

const int N=2e6+10,M=210;
ll n;
int dx[]={-1,1,0,0};
int dy[]={0,0,-1,1};
struct nod2{
	int x,y;
	bool ifa;
};
nod2 ans[N];
void solve(){
	cin >> n;
	map<PII,int>mp;
	for(int i=1;i<=n;i++){
		int x,y; cin >> x >> y;
		mp[{x,y}]=i;
	}
	struct nod{
		int x,y;
		PII an;
	};
	queue<nod>qu;
	for(auto [xy,i]:mp){
		int x=xy.first,y=xy.second;
		for(int j=0;j<4;j++){
			int xx=x+dx[j],yy=y+dy[j];
			if(!mp.count({xx,yy})){
				qu.push({x,y,{xx,yy}}),ans[i]={xx,yy,1};
				break;
			}
		}
	}
	while(!qu.empty()){
		nod t=qu.front(); qu.pop();
		auto [a1,a2]=t.an;
		for(int i=0;i<4;i++){
			int x=t.x+dx[i],y=t.y+dy[i];
			if(mp.count({x,y}) && !ans[mp[{x,y}]].ifa){
				ans[mp[{x,y}]]={a1,a2,1};
				qu.push({x,y,{a1,a2}});
			}
		}
	}
	for(int i=1;i<=n;i++) cout << ans[i].x << " " << ans[i].y << "\n";
}

补 cf edu round124 C                                           cf传送门

思路:画下图发现a的头尾和b的头尾这四个端点必须和对面连线,开始思路不清晰写出一坨,实际分类讨论下边数的情况就行,先把四个端点连接的最小代价求出,然后讨论连四条,两条,三条

代码如下:

const int N=2e5+10,M=210;
ll n;
ll a[N],b[N];
void solve(){
	cin >> n;
	for(int i=1;i<=n;i++) cin >> a[i];
	for(int i=1;i<=n;i++) cin >> b[i];
	ll a1=1e18,an=1e18,b1=1e18,bn=1e18;
	for(int i=1;i<=n;i++){
		a1=min(abs(b[i]-a[1]),a1);
		an=min(abs(b[i]-a[n]),an);
		b1=min(abs(a[i]-b[1]),b1);
		bn=min(abs(a[i]-b[n]),bn);
	}
	ll ans=a1+an+b1+bn;
	ans=min({
		ans,
		abs(a[1]-b[1])+abs(a[n]-b[n]),
		abs(a[1]-b[n])+abs(a[n]-b[1]),
		abs(a[1]-b[1])+an+bn,
		abs(a[1]-b[n])+an+b1,
		abs(a[n]-b[1])+a1+bn,
		abs(a[n]-b[n])+a1+b1
	});
	cout << ans << "\n";
}

近期准备重拾期望dp

先重做期望dp入门题 魔镜                                      cf传送门

思路: dp[ i ]表示从第1天到第 i天且高兴的期望天数

对于第 i面魔镜,有两种情况: (以下将 pi/100 简称为 pi

第一种情况:回答漂亮,则高兴,概率为 pi, 天数为 dp[ i-1 ]+1

第二种情况:回答不漂亮,概率为 1-pi, 天数为 dp[ i-1 ]+1+dp[ i ]

则得转移式 dp[ i ] = pi * ( dp[ i-1 ] +1 ) + ( 1-pi ) * ( dp[ i-1 ]+1+dp[ i ] )                                           化简后得 dp[ i ] = 100 * ( dp[ i-1 ]+1 ) / pi  , 线性转移即可

代码如下:

const int mod=998244353;
ll n;
ll qpow(ll a,int n){
	ll res=1;
	while(n){
		if(n&1) (res*=a)%=mod;
		(a*=a)%=mod;
		n>>=1;
	}
	return res;
}
void solve(){
	cin >> n;
	ll ans=0;
	for(int i=1;i<=n;i++){
		int p; cin >> p;
		ans=100*(ans+1)%mod*qpow(p,mod-2)%mod;
	}
	cout << ans;
}

期望dp入门题2 逃离这棵树                                    mtj传送门

思路: dp[ i ]表示从 i开始到叶子节点即逃离的期望时间,叶子节点的期望时间即为0

对于一个非叶子节点 x,存在两种情况: (以下将移动和不动的概率简称为 q和 p

第一种情况:移动到了一个子节点 v,概率为qv,时间为 dp[ v ]+1

第二种情况:原地不动,概率为 px,时间为 dp[ x ]+1

则得转移式: dp[x]=(1+\sum (qv*dp[v]))/(1-px)                                                              化简后为: dp[x]=(sum+\sum (qv*dp[v]))/(sum-px)

代码如下:

const int N=2e6+10;
const int mod=998244353;
ll n;
ll qpow(ll a,int n){
	ll res=1;
	while(n){
		if(n&1) (res*=a)%=mod;
		(a*=a)%=mod;
		n>>=1;
	}
	return res;
}
int p[N];
ll dp[N];
vector<PII>ve[N];
void dfs(int x){
	ll sum=p[x],qdpv=0;       //注意qdpv需开ll
	for(auto [q,v]:ve[x]){
		sum+=q;
		dfs(v);
		qdpv+=q*dp[v];
	}
	dp[x]=(sum+qdpv)%mod*qpow(sum-p[x],mod-2)%mod;
}
void solve(){
	cin >> n;
	for(int i=1;i<=n;i++) cin >> p[i];
	for(int i=2;i<=n;i++){
		int f,q; cin >> f >> q;
		ve[f].push_back({q,i});
	}
	dfs(1);
	cout << dp[1];
}

状压dp题单第一题 互不侵犯                                 牛客传送门

思路:状压dp板子

代码如下:

ll n;
int s[1<<10],ma[1<<10],cnt;
ll dp[11][110][1<<10];
void solve(){
	int k; cin >> n >> k;
	for(int mask=0;mask<1<<n;mask++){
		if(mask&(mask<<1)) continue;
		ma[cnt++]=mask;                  //注意是cnt++
		s[mask]=__builtin_popcount(mask);
	}
	dp[0][0][0]=1;
	for(int i=1;i<=n+1;i++){
		for(int j=0;j<=k;j++){
			for(int a=0;a<cnt;a++){
				for(int b=0;b<cnt;b++){
					if(j<s[ma[a]]+s[ma[b]]) continue;
					if(ma[a]&ma[b] || ma[a]&ma[b]<<1 || ma[a]&ma[b]>>1) continue;
					dp[i][j][a]+=dp[i-1][j-s[ma[a]]][b];
				}
			}
		}
	}
	cout << dp[n+1][k][0];
}

星期二:

补24钉耙编程联赛2 1006                                     smu传送门

思路:单独算出每个节点的期望停留时间,寻找一条最长的链

一件事成功概率为p,那么完成它的期望时间则为 1/p,可以先把期望时间的分母都化为1-15的lcm最后再约分

代码如下:

const int N=2e6+10,M=210;
ll n;
vector<int>ve[N];
int lc=360360;     //lcm(1,2...15)
int p[N];
ll ans;
void dfs(int x,int f,ll sum){
	ans=max(sum,ans);
	for(int v:ve[x]) if(v!=f)
		dfs(v,x,sum+p[v]);
}
void solve(){
	cin >> n;
	for(int i=1;i<=n;i++) ve[i].clear();
	for(int i=1;i<n;i++){
		int u,v; cin >> u >> v;
		ve[u].push_back(v);
		ve[v].push_back(u);
	}
	for(int i=1;i<=n;i++){
		int pi; cin >> pi;
		p[i]=15*lc/pi;
	}
	ans=0;
	dfs(1,0,p[1]);
	int gc=__gcd(ans,1ll*lc);
	cout << ans/gc << "/" << lc/gc << "\n";
}

24牛客多校3 B                                                      牛客传送门

思路:赛时被折磨半天,最后猜了个gcd的结论过了,然后osir反应过来是裴蜀定理

代码如下:

ll n;
void solve(){
	ll D; cin >> n >> D;
	ll gc=0;
	for(int i=1;i<=n;i++){
		ll h; cin >> h;
		gc=__gcd(gc,h);
	}
	cout << min(D%gc,abs(D%gc-gc));   //注意判反弹的情况
}

补 ABC344 F                                                        atc传送门

思路:很挑战我能力的一道dp,dp【i】【j】【0/1】表示到达 ( i, j )最少的步数及剩余金钱

dis【k】【y】表示从 i,j到点的最少需要金钱。可想到停留拿钱的P一定是递增的关系,且攒到足够走的钱就走,四重枚举( i,j )走到( k,y )的最少步数和剩余金钱,攒钱点之间的移动取最小代价走就行

代码如下:

ll n;
ll p[88][88],r[88][88],d[88][88];
ll dis[88][88],dp[88][88][2];
void solve(){
	cin >> n;
	for(int i=1;i<=n;i++)
		for(int j=1;j<=n;j++)
			cin >> p[i][j],dp[i][j][0]=1e18;
	for(int i=1;i<=n;i++)
		for(int j=1;j<n;j++)
			cin >> r[i][j];
	for(int i=1;i<n;i++)
		for(int j=1;j<=n;j++) 
			cin >> d[i][j];
	dp[1][1][0]=dp[1][1][1]=0;
	for(int i=1;i<=n;i++){
		for(int j=1;j<=n;j++){
			memset(dis,0x3f,sizeof dis);
			dis[i][j]=0;
			for(int k=i;k<=n;k++)
				for(int y=j;y<=n;y++){
					if(k>1) dis[k][y]=min(dis[k-1][y]+d[k-1][y],dis[k][y]);
					if(y>1) dis[k][y]=min(dis[k][y-1]+r[k][y-1],dis[k][y]);
				}
			for(int k=i;k<=n;k++)
				for(int y=j;y<=n;y++){
					if(k!=n && y!=n && p[k][y]<=p[i][j]) continue;
					auto [step,coin]=dp[i][j];
					ll stay=max(0ll,(dis[k][y]-coin+p[i][j]-1)/p[i][j]); //向上取整
					ll ncoin=coin+stay*p[i][j]-dis[k][y];
					ll nstep=step+stay+k-i+y-j;
					if(nstep<dp[k][y][0] || nstep==dp[k][y][0] && ncoin>dp[k][y][1]){
						dp[k][y][0]=nstep,dp[k][y][1]=ncoin;    //&&优先级更高
					}
				}
		}
	}
	cout << dp[n][n][0];
}

补cf round500 div1 B                                          cf传送门

思路:初见这题没有思路,没想到是要往图论的方向去想.

n行m列可以看为是一个二分图,左边n个点,右边m个点,而(n,m)的标记即为连接n点和m点的一条边,那么最后则是要把此图变为完全二分图,也就是存在 n*m条边

通过给出的操作,能将图上的连通块变为完全连通块,所以加边则是要把连通块连一起即可,答案即为连通块的数量减一

代码如下:

const int N=2e6+10;
ll n;
int fa[N];
int fnd(int x){
	return fa[x]==x?x:fa[x]=fnd(fa[x]);
}
void solve(){
	int m,q; cin >> n >> m >> q;
	for(int i=1;i<=n+m;i++) fa[i]=i;
	while(q--){
		int r,c; cin >> r >> c;
		c+=n;                   //防止编号重复
		r=fnd(r),c=fnd(c);
		if(r!=c) fa[r]=c;
	}
	ll ans=-1;
	for(int i=1;i<=n+m;i++) ans+=(fa[i]==i);
	cout << ans;
}

星期三:

洛谷P3388 tarjan割点                                        洛谷传送门

思路:tarjan求割点的模板

代码如下:

const int N=2e6+10,M=210;
ll n;
vector<int>ve[N];
int dfn[N],low[N],tot;
bool cut[N];
int root;
void tarjan(int x){
	dfn[x]=low[x]=++tot;
	int child=0;
	for(int v:ve[x]){
		if(!dfn[v]){
			tarjan(v);
			low[x]=min(low[v],low[x]);
			if(low[v]>=dfn[x]){
				child++;
				if(x!=root || child>1) cut[x]=1;  //ans++跟在这后面会出错,目前不到为啥
			}
		}else low[x]=min(dfn[v],low[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);
		ve[v].push_back(u);
	}
	for(int i=1;i<=n;i++) if(!dfn[i]) root=i,tarjan(i);
	int ans=0;
	for(int i=1;i<=n;i++) ans+=(cut[i]);
	cout << ans << "\n";
	for(int i=1;i<=n;i++) if(cut[i]) cout << i << " ";
}

河南萌新联赛 二,被模拟题折磨的神志不清,一度摆烂,最后看见一道字符串哈希,ac完成救赎

                                                                    牛客传送门

思路:一眼字符串哈希,处理出a的26个字母的目标哈希值,再枚举移多少位,比较哈希值

注意没出现过的字符不参与比较,否则也会计入答案

代码如下:

const int mod=998244353;
ll n;
ll num[27],ha[N][27];
ll p[N];
const int ba=13331;
int g[27];
void solve(){
	cin >> n;
	string a,b; cin >> a >> b; a=" "+a,b=" "+b;
	p[0]=1;
	for(int i=1;i<=n;i++){
		g[a[i]-'a'+1]=g[b[i]-'a'+1]=-1;
		p[i]=p[i-1]*ba%mod;
		for(int j=1;j<=26;j++){
			char c='a'+j-1;
			num[j]=(num[j]*ba+(a[i]==c))%mod;
			ha[i][j]=(ha[i-1][j]*ba+(b[i]==c))%mod;
		}
	}
	int ans=0;
	for(int i=0;i<=n;i++){               //枚举移i位,注意从0开始
		for(int j=1;j<=26;j++) if(g[j]==-1){      //枚举字母
                //假如b为"abcd",现在要得到"cd ab"的哈希值
			ll h=(ha[n][j]-ha[i][j]*p[n-i]%mod+mod)%mod*p[i]%mod; //cd的哈希值
			(h+=ha[i][j])%=mod;                //ab的哈希值
			if(h==num[j]) ans++,g[j]=1;
		}
	}
	cout << ans;
}

星期四:

洛谷P1656                                                           洛谷传送门

思路:tarjan求割边板子题

代码如下:

const int N=2e6+10,M=210;
ll n;
vector<PII>eg;           //存边
vector<int>ve[N];          //存点的出边
int dfn[N],low[N],tot;
PII bri[N];int cnt;      //存割边
void add(int u,int v){
	eg.push_back({u,v});
	ve[u].push_back(eg.size()-1);      //存出边的编号
}
void tarjan(int x,int in_e){        //点和入边
	dfn[x]=low[x]=++tot;
	for(int i=0,sz=ve[x].size();i<sz;i++){
		int j=ve[x][i],v=eg[j].second;       //边和目标点
		if(!dfn[v]){
			tarjan(v,j);
			low[x]=min(low[v],low[x]);
			if(low[v]>dfn[x]) bri[++cnt]={x,v};  //满足条件则此边为桥
		}else if(j!=(in_e^1)) low[x]=min(dfn[v],low[x]);    //j需不是入边的反边
	}
}
void solve(){
	int m; cin >> n >> m;
	while(m--){
		int a,b; cin >> a >> b;
		add(a,b),add(b,a);
	}
	for(int i=1;i<=n;i++) if(!dfn[i]) tarjan(i,-1);
	sort(bri+1,bri+cnt+1);
	for(int i=1;i<=cnt;i++) cout << bri[i].first << " " << bri[i].second << "\n";
}

补24河南萌新 一 L                                                 牛客传送门

思路:tarjan割边+多重背包优化的板子题

tarjan把割边标记出来,dfs处理出连通块数量和大小,然后背包处理可行性,然而把所有连通块用01背包处理显然会超时,我们把相同大小的连通块放一起,用多重背包处理,以及加上一点优化

代码如下(二进制优化多重背包 :

const int N=2e6+10,M=210;
ll n;
vector<PII>eg;
vector<int>ve[N];
int dfn[N],low[N],tot;
//PII bri[N];int cnt;
bool bri[N],vi[N];       //由存桥改为对桥打上标记
void add(int u,int v){
	eg.push_back({u,v});
	ve[u].push_back(eg.size()-1);
}
void tarjan(int x,int in_e){
	dfn[x]=low[x]=++tot;
	for(int i=0,sz=ve[x].size();i<sz;i++){
		int j=ve[x][i],v=eg[j].second;
		if(!dfn[v]){
			tarjan(v,j);
			low[x]=min(low[v],low[x]);
//			if(low[v]>dfn[x]) bri[++cnt]={x,v};
			if(low[v]>dfn[x]) bri[j]=bri[j^1]=1;
		}else if(j!=(in_e^1)) low[x]=min(dfn[v],low[x]);
	}
}
int dfs(int x){
	int res=1;
	for(int i=0,sz=ve[x].size();i<sz;i++){
		int j=ve[x][i],v=eg[j].second;
		if(vi[v] || bri[j]) continue;
		vi[v]=1;
		res+=dfs(v);
	}
	return res;
}
ll dp[N];
void solve(){
	int m; cin >> n >> m;
	while(m--){
		int a,b; cin >> a >> b;
		add(a,b),add(b,a);
	}
	for(int i=1;i<=n;i++) if(!dfn[i]) tarjan(i,-1);
	map<int,int>mp;
	for(int i=1;i<=n;i++) if(!vi[i]) vi[i]=1,mp[dfs(i)]++;
	vector<PII>blo;ll sum=0;  //fi-价值,se-重量,不过此题中重量和价值相等
	for(auto [v,s]:mp){
		for(ll i=1;i<=s;i<<=1){
			blo.push_back({i*v,i*v}),sum+=i*v;
			s-=i;
		}
		if(s) blo.push_back({s*v,s*v}),sum+=s*v;   //二进制优化
	}
	dp[0]=1;
	for(int i=0,sz=blo.size();i<sz;i++)
		for(int j=sum;j>=blo[i].second;j--) dp[j]+=dp[j-blo[i].second];
	ll ans=0;
	for(int i=1;i<=sum;i++) if(dp[i]) ans=max(1ll*i*(sum-i),ans);
	cout << ans << "\n";
}

星期五:

补 ABC225 F                                                       atc传送门

思路:用到了一个很新的排序,实际上把s作为base进制数推导一下,其实是具有排序的传递性

关于为什么要倒着dp,似懂非懂,还需沈科历届

代码如下:

ll n;
string a[55];
string dp[55][55];
void solve(){
	int k; cin >> n >> k;
	for(int i=1;i<=n;i++){
		cin >> a[i];
		for(int j=1;j<=k;j++) dp[i][j]="zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz";
	}
//	for(int j=1;j<=k;j++) dp[0][j]="zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz";
	for(int j=1;j<=k;j++) dp[n+1][j]="zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz";
	sort(a+1,a+n+1,[](string s1,string s2){
		return s1+s2<s2+s1;
	});
//	for(int i=1;i<=k;i++) s.append(a[i]);
	
//	for(int i=1;i<=n;i++){
//		for(int j=1;j<=k;j++){
//			dp[i][j]=min(dp[i-1][j-1]+a[i],dp[i-1][j]);
//		}
//	}
	for(int i=n;i;i--){
		for(int j=1;j<=k;j++)
			dp[i][j]=min(a[i]+dp[i+1][j-1],dp[i+1][j]);
	}
//	for(int i=1;i<=n;i++) cout << a[i] << " \n"[i==n];
//	for(int i=1;i<=n;i++){
//		for(int j=1;j<=k;j++) cout << dp[i][j] << " \n"[j==k];
//	}
	cout << dp[1][k];
}

多重背包单调队列优化 洛谷P1776                  洛谷传送门

思路:练习多重背包的板子,虽跟着视频学了并敲了单调队列优化,仍需沈科历届

代码如下:

const int N=2e6+10,M=210;
ll n;
ll dp[N][2];
int q[N];
void solve(){
	ll m; cin >> n >> m;
	for(int i=1;i<=n;i++){
		ll v,w,s; cin >> v >> w >> s;
		for(int d=0;d<w;d++){
			int h=1,t=0;
			for(int j=d;j<=m;j+=w){
				while(h<=t && dp[j][0]>=dp[q[t]][0]+(j-q[t])/w*v) t--;
				q[++t]=j;
				if(h<=t && q[h]<j-s*w) h++;
				if(h<=t) dp[j][1]=max(dp[q[h]][0]+(j-q[h])/w*v,dp[j][0]);
			}
		}
		for(int j=0;j<=m;j++) dp[j][0]=dp[j][1],dp[j][1]=0;
	}
	cout << dp[m][0];
}

补 ABC156 D                                                      atc传送门

思路:之前那个组合数不能取模用不上了,用逆元求组合数可行,复杂度为O(m)

代码如下:

ll n;
ll qpow(ll a,ll n){
	ll res=1;
	while(n){
		if(n&1) (res*=a)%=mod;
		(a*=a)%=mod;
		n>>=1;
	}
	return res;
}
ll c(int n,int m){
	ll fz=1,fm=1;
	for(int i=1;i<=m;i++) (fm*=i)%=mod;
	for(int i=n;i>n-m;i--) (fz*=i)%=mod;
	return fz*qpow(fm,mod-2)%mod;
}
void solve(){
	int a,b; cin >> n >> a >> b;
	ll ans=(qpow(2,n)-1+mod)%mod;
	ans=(ans-c(n,a)+mod)%mod;
	ans=(ans-c(n,b)+mod)%mod;
	cout << ans << "\n";
}

星期六:

补 ABC178 C                                                    atc传送门

思路:开局想到一个错解,Cn,2 * 10^(n-2),会有大量重复的情况

考虑容斥原理,所有序列有10^n种,减去没有0和没有9的9^n种序列,再加上重复减去的即没0也没9的8^n种情况

代码如下:

ll n;
ll qpow(ll a,ll n){
	ll res=1;
	while(n){
		if(n&1) (res*=a)%=mod;
		(a*=a)%=mod;
		n>>=1;
	}
	return res;
}
void solve(){
	cin >> n;
	ll ans=(qpow(10,n)-2*qpow(9,n)+qpow(8,n))%mod;
	(ans+=mod)%=mod;
	cout << ans;
}

补 ABC263 D                                                    atc传送门

思路:挺简单一题,但赛时没出。枚举 x的取值,在 i+1到 n中找到最小值的 y,和ans取min即

赛时在找最小值的操作中想用线段树,但捣鼓了下发现并不能实现,首先寻找最小值是有范围限制的,其次其实找的是最小值的下标,试了下线段树上二分,发现也不能实现,因为不能知道子节点的最小值是不是在范围内的

其实完全不需要那么麻烦,整个右操作的后缀最小值数组,开pair类型second带下标即可

更新:其实下标也不需要带,只需要维护后缀最小值即可,那这样又可以用线段树了

代码如下:

const int N=2e6+10,M=210;
ll n;
ll a[N];
PII ar[N];
void solve(){
	ll l,r; cin >> n >> l >> r;
	for(int i=1;i<=n;i++){
		cin >> a[i];
		a[i]+=a[i-1];
	}
	ar[n+1]={a[n],n+1};
	for(int i=n;i;i--){
		ar[i].first=r*(n-i+1)+a[i-1];
		if(ar[i].first<ar[i+1].first) ar[i].second=i;
		else ar[i]=ar[i+1];
	}
	ll ans=1e18;
	for(int i=0;i<=n;i++){
		ll al=i*l;
//		int idx=ar[i+1].second;
//		ans=min(al+ar[idx].first-a[i],ans);
        ans=min(al+ar[i+1].first-a[i],ans);
	}
	cout << ans;
}

补 ABC272 E                                                        atc传送门

思路:开局毫无头猪,想不到什么算法能够实现,其实也确实不用什么算法,因为就是暴力

长度为 n序列 mex的范围也必在 [0,n-1]间,所以对于 ai只考虑其值在范围内的操作次数范围,i为1时有效操作最多 n次,i为2时有效操作次数最多 n/2次,为3最多n/3次……,又遇到了一个熟悉又陌生的玩意儿:调和级数。1+1/2+1/3+1/4+...近似于 logn,所以上述暴力复杂度为 O(nlogn)

代码如下:

const int N=2e6+10,M=210;
ll n;
ll a[N];
vector<int>ve[N];
void solve(){
	ll m; cin >> n >> m;
	for(int i=1;i<=n;i++){
		cin >> a[i];
		if(a[i]>=n) continue;
		int l=a[i]>=-i?1:(-a[i]+i-1)/i;
		int r=min(m,(n-a[i])/i);          //有效操作次数的范围
		for(int j=l;j<=r;j++) ve[j].push_back(a[i]+1ll*i*j);
	}
	for(int i=1;i<=m;i++){
		unordered_map<int,bool>ex;
		for(auto v:ve[i]) ex[v]=1;
		int ans=0;
		while(ex[ans]) ans++;
		cout << ans << "\n";
	}
}

补 ABC199 E                                                       atc传送门

思路:神奇的状压转移方案,思路不是很清晰,仍需沈科历届

代码如下:

ll n;
ll dp[1<<19];
vector<PII>ve[110];
void solve(){
	int m; cin >> n >> m;
	for(int i=1;i<=m;i++){
		int x,y,z; cin >> x >> y >> z;
		ve[x].push_back({y,z});
	}
	dp[0]=1;
	for(int mask=0;mask<1<<n;mask++){
		vector<int>num;
		for(int i=0;i<n;i++) if(mask&1<<i) num.push_back(i+1);
		bool ifz=1;
		for(auto [y,z]:ve[__builtin_popcount(mask)]){
			int sum=0;
			for(auto v:num) sum+=(v<=y);
			if(sum>z){ifz=0; break;}
		}
		if(ifz) for(auto i:num) dp[mask]+=dp[mask^(1<<i-1)];
	}
	cout << dp[(1<<n)-1];
}

周日:

做道24钉耙编程联赛3的线段树                              vj传送门

思路: 用sta变量维护一个区间的状态,2表示元素相同,3表示升序,4表示降序,5表示单峰,-1非法状态

这样写的坏处是merge会写成依托,因为 sta的合并需要很详细的分类讨论,在此不赘述

代码如下:

const int N=2e6+10,M=210;
ll n;
struct seg_Tree{
#define lc p<<1
#define rc p<<1|1
	struct nod{
		int l,r;
		ll le,ri;       //左右端点的值
		ll add;
		int sta;    //表示区间的状态
	}t[N];
	ll ql,qr,qv;
	nod merge(nod a,nod b){
		nod res;
		res.l=a.l,res.r=b.r;
		res.add=0;
		res.le=a.le,res.ri=b.ri;
		if(a.sta==2){
			if(a.l==a.r){
				if(b.l==b.r){
					if(a.ri==b.le) res.sta=2;
					if(a.ri<b.le) res.sta=3;
					if(a.ri>b.le) res.sta=4;
				}else{
					if(b.sta==2 && a.ri==b.le) res.sta=2;
					else if(b.sta==3 && a.ri<b.le) res.sta=3;
					else if(b.sta==4 && a.ri>b.le) res.sta=4;
					else if(b.sta==4 && a.ri<b.le) res.sta=5;
					else if(b.sta==5 && a.ri<b.le) res.sta=5;
					else res.sta=-1;
				}
			}else{
				if(b.sta==2 && a.ri==b.le) res.sta=2;
				else res.sta=-1;
			}
		}else if(a.sta==3){
			if(b.sta==3 && a.ri<b.le) res.sta=3;
			else if(b.sta==4) res.sta=5;
			else if(b.sta==5 && a.ri<b.le) res.sta=5;
			else if(b.sta==2 && b.l==b.r){
				if(a.ri<b.le) res.sta=3;
				else if(a.ri>b.le) res.sta=5;
				else res.sta=-1;
			}else res.sta=-1;
		}else if(a.sta==4){
			if(b.sta==4 && a.ri>b.le) res.sta=4;
			else if(b.sta==2 && b.l==b.r && a.ri>b.le) res.sta=4;
			else res.sta=-1;
		}else if(a.sta==5){
			if(b.sta==4 && a.ri>b.le) res.sta=5;
			else if(b.sta==2 && b.l==b.r && a.ri>b.le) res.sta=5;
			else res.sta=-1;
		}else res.sta=-1;
		return res;
	}
	void pushup(int p){t[p]=merge(t[lc],t[rc]);}
	void pushdn(int p){
		if(!t[p].add) return ;
		t[lc].le+=t[p].add,t[lc].ri+=t[p].add;
		t[lc].add+=t[p].add;
		t[rc].le+=t[p].add,t[rc].ri+=t[p].add;
		t[rc].add+=t[p].add;
		t[p].add=0;
	}
	void bd(int p,int l,int r){
		t[p]={l,r,0,0,0,2};
		if(l==r){
			int num; cin >> num;
			t[p].le=t[p].ri=num;
			return ;
		}
		int mid=l+r>>1;
		bd(lc,l,mid);
		bd(rc,mid+1,r);
		pushup(p);
	}
	void update(int p){
		if(ql<=t[p].l && qr>=t[p].r){
			t[p].le+=qv,t[p].ri+=qv;
			t[p].add+=qv;
			return ;
		}
		int mid=t[p].l+t[p].r>>1;
		pushdn(p);
		if(ql<=mid) update(lc);
		if(qr>mid) update(rc);
		pushup(p);
	}
	void updt(int l,int r,ll v){
		ql=l,qr=r;
		qv=v;
		update(1);
	}
	nod query(int p){
		if(ql<=t[p].l && qr>=t[p].r) return t[p];
		int mid=t[p].l+t[p].r>>1;
		pushdn(p);
		if(ql>mid) return query(rc);
		if(qr<=mid) return query(lc);
		return merge(query(lc),query(rc));
	}
	int ask(int l,int r){
		ql=l,qr=r;
		return query(1).sta;
	}
}tr;
void solve(){
	cin >> n;
	tr.bd(1,1,n);
	int q; cin >> q;
	while(q--){
		int op; cin >> op;
		int l,r; cin >> l >> r;
		if(op==1){
			int x; cin >> x;
			tr.updt(l,r,x);
		}else{
			if(l==r) op=2;
			int sta=tr.ask(l,r);
			if(sta==op) cout << "1\n";
			else cout << "0\n";
		}
	}
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值