AtCoder Beginner Contest 248
很好的一道题,可以学习一下当数据范围较大时怎么优化。
题意:给定N,问有多少种序列 A N A_N AN满足下列条件:
- 1 ≤ A i ≤ M 1 \leq A_i \leq M 1≤Ai≤M.
- ∑ i = 1 N A i ≤ K \sum_{i=1}^{N}A_i \leq K ∑i=1NAi≤K.
1 ≤ N , M ≤ 50 1\leq N,M \leq 50 1≤N,M≤50 , N ≤ K ≤ N ∗ M N\leq K\leq N*M N≤K≤N∗M
问题要求的是方案数,且数据范围比较小,可以想到用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[i−1][j]−sum[i−1][j−a[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;
}
题意:给定一个序列,询问q次,询问L~R区间内X的个数。
乍一看需要用数据结构来做,但是仔细想想其实不用。不过主席树确实能写,把板子套过来就行。
这里给出两种做法。
第一种:首先观察到数组元素的值不是很大,因此我们可以暴力的开 2 ∗ 1 0 5 2*10^5 2∗105个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;
}
题意:给定一些点的坐标,问有多少条直线,经过至少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∗(k−1)/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;
}
题意:给定一个联通的图,如图:
问如果删去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;
}