ARC 179 & Codeforces Round 955 (Div. 2, with prizes from NEAR!)

ARC

submissions

A

这道题我还不会证明哎。

可以猜想,序列要么是正着排要么是倒着排的。如果都不可以的就输出 No。具体来说,\(k>0\) 正着,\(k<0\) 倒着。

证明,待补。

B

看到 \(m\le 10\) 就想到一定是 bitmask dp 啦!设 \(dp_{i,msk}\) 为目前填了 \(i\),下一步可以填 \(msk\) 里面的数的方案数。枚举填什么就可以,\(msk\) 的维护就是如果在中间出现了自己对应的那个数就把这一位变成 \(1\)

时间复杂度 \(\mathcal{O}(n2^mm)\)

C

先说一个错误的做法。

很容易想到,第一次一定要先排一个序,否则比较难搞。排完序我们首尾配对一直到中间,配成 \(\frac{len}{2}\) 个数(原先 \(len\) 个数),再排序,重复这个过程。

为什么是错的呢?考虑一个例子:\(n=4,r=10,a=\{-10,5,6,9\}\)。这样 \(|5+6|>r=10\),所以不行。

但是我们发现如果只是首尾配对,不配对其他的,一定是可行的。因为:如果都是正数,很显然;如果一个负一个正,加起来一定不超过绝对值的最大值。我们配对以后直接二分插入的位置就可以了。

关于排序有一个 stl 的技巧:stable_sort(id+1,id+1+n,cmp);,其中 cmp 函数中询问。

CF

A

显然,\(x_1,y_1\)\(x_2,y_2\) 的大小关系不能变才能是 Yes

B

我写的是一个玄学复杂度的!就是,我们可以在 \(x\ge y\) 的时候已知暴力(其实加的操作可以直接通过余数算出),等到到了 \(x<y\),除了第一次以外,后面就是循环,可以容易求出。

时间复杂度可以感性理解为 \(\mathcal{O}(\log_y x)\)

Code
#include <bits/stdc++.h>

using namespace std;

using ll = long long;

int main(){
	ios::sync_with_stdio(false);
	cin.tie(0);

	int t;
	cin>>t;
	while (t--){
		ll x,y,k,f=0;
		cin>>x>>y>>k;
		while (k>0 && x>=y){
			ll m=x/y;
			m++;
			m*=y;
			ll t=m-x;
			if (t>k){
				cout<<x+k<<"\n";
				f=1;
				break;
			}
			else{
				x+=t;
				k-=t;
				while (x%y==0){
					x/=y;
				} 
			}
		}
		if (f){
			continue;
		}
		if (k==0){
			cout<<x<<"\n";
			continue;
		}
		ll t=y-x;
		if (t>k){
			cout<<x+k<<"\n";
		}
		else{
			k-=t,x=1;
			ll m=y-1;
			ll r=k%m;
			cout<<x+r<<"\n";
		}
	}
	return 0;
}

C

很显然的 dp。设 \(dp_i\) 为考虑到 \(i\) 的最大答案,其中一个转移是 \(dp_i=dp_{i-1}\)。如果用,我们只需要求出可以转移的左右区间即可,这个用二分。区间是单调往右走的,用一个 two pointers 和 set 维护即可。

Code
#include <bits/stdc++.h>

using namespace std;

using ll = long long;

const int N = 1e5+5;

ll n,L,R,a[N],s[N],dp[N];

ll fl(int p){
	int l=-1,r=p;
	while (l+1<r){
		int mid=l+r>>1;
		if (s[p]-s[mid]<=R){
			r=mid;
		}
		else{
			l=mid;
		}
	}
	return r;
}

ll fr(int p){
	int l=-1,r=p;
	while (l+1<r){
		int mid=l+r>>1;
		if (s[p]-s[mid]>=L){
			l=mid;
		}
		else{
			r=mid;
		}
	}
	return l;
}

void solve(){
	cin>>n>>L>>R;
	for (int i=1; i<=n; i++){
		dp[i]=0;
		cin>>a[i];
		s[i]=s[i-1]+a[i];
	}
	int pl=-1,pr=-1;
	dp[0]=0;
	multiset<ll> st; 
	for (int i=1; i<=n; i++){
		int lb=fl(i),rb=fr(i);
		if (lb<=rb){
			while (pr<rb){
				pr++;
				st.insert(-dp[pr]);
			}
			while (pl+1<lb){
				pl++;
				st.erase(st.find(-dp[pl]));
			}
		}
		if (st.size()){
			dp[i]=-(*st.begin())+1;
		}
		dp[i]=max(dp[i],dp[i-1]);
	}
	cout<<dp[n]<<"\n";
}

int main(){
	ios::sync_with_stdio(false);
	cin.tie(0);

	int t;
	cin>>t;
	while (t--){
		solve();
	}
	return 0;
}

D

我们可以用二维前缀和求出每一个 \(k\times k\) 的矩形我们令 \(c=1\) 的贡献是什么。这样问题就变为有若干个数,可以选无数次,能不能和等于另一个数。这个用裴蜀定理即可,具体的,能组成的数一定是那若干个数的 \(\gcd\)

时间复杂度 \(\mathcal{O}(n^2\log)\)

Code
#include <bits/stdc++.h>

using namespace std;

using ll = long long;

const int N = 5e2+2;

ll n,m,k,a[N][N],c[N][N];
string s[N];

ll cal(int x1,int y1,int x2,int y2){
	return c[x2][y2]-c[x1-1][y2]-c[x2][y1-1]+c[x1-1][y1-1];
}

ll cal1(int x1,int y1,int x2,int y2){
	return a[x2][y2]-a[x1-1][y2]-a[x2][y1-1]+a[x1-1][y1-1];
}

void solve(){
	cin>>n>>m>>k;
	for (int i=1; i<=n; i++){
		for (int j=1; j<=m; j++){
			cin>>a[i][j];
		}
	}
	for (int i=1; i<=n; i++){
		cin>>s[i];
		s[i]=" "+s[i];
		for (int j=1; j<=m; j++){
			if (s[i][j]=='0'){
				c[i][j]=-1;
				a[i][j]*=-1;
			}
			else{
				c[i][j]=1;
			}
		}
	}
	for (int i=1; i<=n; i++){
		for (int j=1; j<=m; j++){
			c[i][j]+=c[i-1][j]+c[i][j-1]-c[i-1][j-1];
			a[i][j]+=a[i-1][j]+a[i][j-1]-a[i-1][j-1];
		}
	}
	ll g=0;
	for (int i=1; i+k-1<=n; i++){
		for (int j=1; j+k-1<=m; j++){
			ll sum=cal(i,j,i+k-1,j+k-1);
			sum=abs(sum);
			g=__gcd(g,sum);
		}
	}
	ll sum=cal1(1,1,n,m);
	if ((g==0 && sum==0) || (g!=0 && sum%g==0)){
		cout<<"YES\n";
	}
	else{
		cout<<"NO\n";
	}
}

int main(){
	ios::sync_with_stdio(false);
	cin.tie(0);

	int t;
	cin>>t;
	while (t--){
		solve();
	}
	return 0;
}

E

有妙妙解法。

\(sol(n,k)\) 为答案。(注意,是 \(0\sim n-1\) 的!)那么,我们 \(sol(n,k)\) 可以通过 \(sol(mx,k)\)\(sol(n-mx,k-1)\) 算出来,其中 \(mx\) 是小于 \(n\) 的最大 \(2\) 次幂。(很好理解,大于等于 \(mx\) 的就会有一位固然是 \(1\),就会是 \(k-1\)。)设 \(mx=2^c\)

问题是怎么算。除了内部的,还有一个端点在左边一个端点在右边的。官方题解是维护了三个值,很复杂。有没有直接算的方法?

我们发现,如果 \(c>k\),那么左边最后一个就会有大于 \(k\)\(1\),没有贡献,所以必须 \(c\le k\)。这个时候左边所有的都满足,只需要计算右边的。右边的到了 \(2^k-1\) 也不行,因为这样也超过了(当然要和长度取最小值)。所以我们算出了右边的贡献 \(s=\min(2^k-1,n-2^c)\)。那么,答案就要多加上 \(s\cdot 2^c\)

直接记忆化搜索即可,复杂度是 \(\mathcal{O}(k \log n)\) 的。代码非常短。

Code
#include <bits/stdc++.h>

using namespace std;

using ll = long long;

const ll mod = 1e9+7;

map<pair<ll,ll>,ll> mp;

ll sol(ll n,ll k){
	if (k==0 || n==1){
		return 1;
	}
	if (mp.count({n,k})){
		return mp[{n,k}];
	}
	ll c=0;
	while ((1ll<<c+1)<n){
		c++;
	}
	ll l=sol(1ll<<c,k),r=sol(n-(1ll<<c),k-1);
	ll ans=l+r;
	if (c<=k){
		ll s=min((1ll<<k)-1,n-(1ll<<c));
		ans+=s%mod*((1ll<<c)%mod)%mod;
	}
	mp[{n,k}]=ans%mod;
	return ans%mod;
}

void solve(){
	ll n,k;
	cin>>n>>k;
	cout<<sol(n,k)<<"\n";
}

int main(){
	ios::sync_with_stdio(false);
	cin.tie(0);

	int t;
	cin>>t;
	while (t--){
		solve();
	}
	return 0;
}
posted @ 2024-06-26 22:35  SFlyer  阅读(36)  评论(0编辑  收藏  举报