AtCoder Beginner Contest 248 C~F题解

AtCoder Beginner Contest 248

C

很好的一道题,可以学习一下当数据范围较大时怎么优化。

题意:给定N,问有多少种序列 A N A_N AN满足下列条件:

  1. 1 ≤ A i ≤ M 1 \leq A_i \leq M 1AiM.
  2. ∑ i = 1 N A i ≤ K \sum_{i=1}^{N}A_i \leq K i=1NAiK.

1 ≤ N , M ≤ 50 1\leq N,M \leq 50 1N,M50 , N ≤ K ≤ N ∗ M N\leq K\leq N*M NKNM

问题要求的是方案数,且数据范围比较小,可以想到用dp来求解。

定义 d p [ i ] [ j ] 为前 i 个数,和为 j 的方案数 dp[i][j]为前i个数,和为j的方案数 dp[i][j]为前i个数,和为j的方案数,那么状态转移为:

for(int x=1;x<=m;x++){
	if(j>=x) dp[i][j]=(dp[i-1][j-x]+dp[i][j])%mod;
}

可以思考一个问题,当数据范围较大时要怎么写。

考虑前缀和优化。

s u m [ i ] [ j ] = ∑ j = 1 n d p [ i ] [ j ] sum[i][j]= \sum_{j=1}^{n}dp[i][j] sum[i][j]=j=1ndp[i][j] , 则状态转移方程为:

d p [ i ] [ j ] = d p [ i ] [ j ] + s u m [ i − 1 ] [ j ] − s u m [ i − 1 ] [ j − a [ i ] − 1 ] dp[i][j]=dp[i][j]+sum[i-1][j]-sum[i-1][j-a[i]-1] dp[i][j]=dp[i][j]+sum[i1][j]sum[i1][ja[i]1], 其中 s u m [ i ] [ j ] 可以由上一层的 d p [ i ] [ j ] 求出 sum[i][j]可以由上一层的dp[i][j]求出 sum[i][j]可以由上一层的dp[i][j]求出

这也对应另一题:题目链接

暴力code:

#include<bits/stdc++.h>

using namespace std;
#define endl '\n'
#define ios std::ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
#define int long long
#define x first
#define y second
typedef pair<int,int> pii;

//head
const int N=50+10,mod=998244353;
int dp[N][N*N];

void work()
{
	int n,m,k;
	cin>>n>>m>>k;
	dp[0][0]=1;
	for(int i=1;i<=n;i++){
		for(int j=1;j<=k;j++){
			for(int x=1;x<=m;x++){
				if(j>=x) dp[i][j]=(dp[i-1][j-x]+dp[i][j])%mod;
			}
		}
	}
	int ans=0;
	for(int i=1;i<=k;i++) ans=(ans+dp[n][i])%mod;
	cout<<ans<<endl;
}

signed main()
{
	//ios;
	int t;
	t=1;
	//cin>>t;
	while(t--) work();

	return 0;
}

前缀和优化code:

#include<bits/stdc++.h>


using namespace std;
#define endl '\n'
#define ios std::ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
#define int long long
#define x first
#define y second
typedef pair<int,int> pii;

//head
const int N=50+10,mod=998244353;

int dp[N][N*N];
int s[N*N];
void work()
{
	int n,m,k;
	cin>>n>>m>>k;
	dp[0][0]=1;
	for(int i=1;i<=m;i++){
		dp[1][i]=1;
	}
	for(int i=2;i<=n;i++){
		for(int j=1;j<=k;j++){
			s[j]=s[j-1]+dp[i-1][j];	
		}
		for(int j=1;j<=k;j++){
			int r=j-1, l=max(1LL,j-m);
			dp[i][j]=((s[r]-s[l-1])%mod+mod)%mod;
		}
	}
	int ans=0;
	for(int i=1;i<=k;i++) ans=(ans+dp[n][i])%mod;
	cout<<ans<<endl;
}

signed main()
{
	//ios;
	int t;
	t=1;
	//cin>>t;
	while(t--) work();

	return 0;
}

D

题意:给定一个序列,询问q次,询问L~R区间内X的个数。

乍一看需要用数据结构来做,但是仔细想想其实不用。不过主席树确实能写,把板子套过来就行。

这里给出两种做法。

第一种:首先观察到数组元素的值不是很大,因此我们可以暴力的开 2 ∗ 1 0 5 2*10^5 2105个vector, 每个vector存的内容是其对应的下标,因此对于每个询问,我们直接对X这个vector使用二分查找有多少个下标在L~R之间。

第二种:思路和第一种类似,把问题转化为有多少个下标在L~R内,我们可以使用piar把每个元素的值和下标存入,然后对其排序,会发现序列会是一段一段的,我们同样使用二分查找即可算出答案,相比于第一种的好处是可以处理元素更大的情况。

时间复杂度均为: O ( q l o g n ) O(qlog n) O(qlogn).

code1:

#include <bits/stdc++.h>
using namespace std;
const int N=2e5+10;
vector<int> idx[N];
int main() {
	int n;
	cin >> n;
	for(int i = 0; i < n; i++) {
		int a;
		cin >> a;
		idx[a].push_back(i);
	}

	int q;
	cin >> q;

	while(q--) {
		int l, r, x;
		cin >> l >> r >> x;
		cout << lower_bound(idx[x].begin(),idx[x].end(),r) - lower_bound(idx[x].begin(), idx[x].end(), l - 1) << endl;
	}
    return 0;
}

code2:

#include<bits/stdc++.h>

using namespace std;
#define endl '\n'
#define ios std::ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
#define int long long
#define x first
#define y second
typedef pair<int,int> pii;

//head
const int N=2e5+10,mod=998244353;
pii a[N];

void work()
{
	int n;
	cin>>n;
	for(int i=1;i<=n;i++){
		int x; cin>>x;
		a[i]={x,i};
	}
	sort(a+1,a+1+n);
	int q;
	cin>>q;
	while(q--)
	{
		int l,r,x;
		cin>>l>>r>>x;
		pii xx={x,l},yy={x,r};
		int L=lower_bound(a+1,a+1+n,xx)-a;
		int R=upper_bound(a+1,a+1+n,yy)-a;
		cout<<R-L<<endl;
	}
}

signed main()
{
	//ios;
	int t;
	t=1;
	//cin>>t;
	while(t--) work();

	return 0;
}

E

题意:给定一些点的坐标,问有多少条直线,经过至少K个点。

感觉这题的关键是怎么判断两条直线相同,这道题的做法我还是用的 n 2 l o g n^2log n2log的,但是判断直线相同给我整的很迷。后面学习了一下,为了方便,常用Ax+By+C=0来表示直线,因此我们要把直线转化成这种形式。但是要注意一个点,要把直线规范化,因为A,B,C,和-A,-B,-C是相同的。

我们可以看每条直线出现了几次,如果其值大于 k ∗ ( k − 1 ) / 2 k*(k-1)/2 k(k1)/2,则说明其经过了至少k个点。(这个值很好推出来,可以自己模拟一下)

直线AX+BY+C=0的一般式方程是:
A = Y2 - Y1
B = X1 - X2
C = X2*Y1 - X1*Y2

code:

#include<bits/stdc++.h>

using namespace std;
#define endl '\n'
#define ios std::ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
#define int long long
#define x first
#define y second
typedef pair<int,int> pii;

//head
const int N=2e5+10,mod=998244353;
pii a[N];
int ans;

void work()
{
	int n,k;
	cin>>n>>k;
	for(int i=1;i<=n;i++){
		int x,y;
		cin>>x>>y;
		a[i]={x,y};
	}
	if(k==1){
		cout<<"Infinity"<<endl;
		return ;
	}
	map<pair<pii,int>,int> mp;
	
	for(int i=1;i<=n;i++){
		for(int j=i+1;j<=n;j++){
			int dx=a[i].x-a[j].x;
			int dy=a[j].y-a[i].y;
			
			int d=abs(__gcd(dx,dy));
			dx/=d; dy/=d;
			int b=-(a[i].y*dx+a[i].x*dy);
			
			if(dx<0){//直线规范化
				dx=-dx;
				dy=-dy;
				b=-b;
			}
			if(dx==0&&dy<0) {//同上
				dy=-dy;
				b=-b;
			}
			mp[{{dx,dy},b}]++; 
		}
	}
	
	for(auto [t,x]:mp)
	{	
		if(x>=k*(k-1)/2) ans++;
	}
	cout<<ans<<endl;
}
signed main()
{
	//ios;
	int t;
	t=1;
	//cin>>t;
	while(t--) work();

	return 0;
}

F

题意:给定一个联通的图,如图:

img

问如果删去1,2,3…n-1条边后仍保持联通的方案数为多少。

这道题有点递推式的感觉,但是直接看还是不好想出来。递推式关键是想出状态怎么来表示。

d p [ i ] [ j ] [ 0 / 1 ] 来表示当前有 i 列,删去了 j 条边,是否保持联通 dp[i][j][0/1]来表示当前有i列,删去了j条边,是否保持联通 dp[i][j][0/1]来表示当前有i列,删去了j条边,是否保持联通的方案数。

转移的过程要在图上列出所有情况,借鉴了一下别人的题解。感觉写的很清晰。

#include<bits/stdc++.h>

using namespace std;
#define endl '\n'
#define ios std::ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
#define int long long
#define x first
#define y second
typedef pair<int,int> pii;

//head
const int N=3e3+10,mod=998244353;

int dp[N][N][2];
//前i列,删除了j条边,0/1表示是否仍保持上下联通的方案数
void work()
{
	int n,p;
	cin>>n>>p;
	dp[1][1][0]=1; dp[1][0][1]=1;
	for(int i=1;i<=n;i++){
		for(int j=0;j<n;j++){
			if(j>=1) dp[i][j][1]+=dp[i-1][j-1][1]*3;
			dp[i][j][1]+=dp[i-1][j][0];
			dp[i][j][1]+=dp[i-1][j][1];
			dp[i][j][1]%=p;
			if(j>=2) {
				dp[i][j][0]+=dp[i-1][j-2][1]*2;
			}
			if(j>=1) dp[i][j][0]+=dp[i-1][j-1][0];
			dp[i][j][0]%=p;
		}
	}
	for(int i=1;i<n;i++){
		cout<<dp[n][i][1]<<' ';
	}
	cout<<endl;
}

signed main()
{
	ios;
	int t;
	t=1;
	//cin>>t;
	while(t--) work();

	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值