考试总结
DP专题考试
这几天考了很多场DP啊,属实是考废了,中途因为唐氏错误保龄了一次,其他几次考的也不是很理想,可能跟最近低迷的状态有关吧。
现在开学停课搞竞赛,先把前几天的DP总结一下。
Day1(2024.8.30)
T1 天平(balance)
题意
有一个杠杆,有若干个秤砣,重量为 \(w_i\),和若干个可以放置秤砣的位置 \(p_i\),求在使用所有秤砣的条件下,有多少种挂秤砣的方案,可以使杠杆平衡(力矩之和为0)。
思路
我们考虑 \(dp_{i,j}\) 表示的是放了前 \(i\) 个秤砣,当前力矩为 \(j\) 的方案数。
对于当前秤砣 \(i\),它放到第 \(j\) 个可以放的位置的力矩为 \(w_i \times p_j\)。
那么就能得到:
再看一眼限制:
\(n,m \le 20\)
直接秒了(为什么时间如此充裕)。
代码
#include<bits/stdc++.h>
#define int long long
#define ull unsigned long long
#define PII pair<int,int>
#define mk(a,b) make_pair(a,b)
using namespace std;
template<typename P>
inline void read(P &x){
P res=0,f=1;
char ch=getchar();
while(ch<'0' || ch>'9'){
if(ch=='-') f=-1;
ch=getchar();
}
while(ch>='0' && ch<='9'){
res=(res<<3)+(res<<1)+(ch^48);
ch=getchar();
}
x=res*f;
}
template<typename PP>
inline void write(PP x){
if(x<0) putchar('-'),x=-x;
if(x>=10) write(x/10);
putchar('0'+x%10);
}
int T=1;
int n,m;
int w[22],s[22],co[22][22];
unordered_map<int,int> dp[22];//前i个力举之和为j
signed main(){
auto solve=[&](){
read(n),read(m);
for(int i=1;i<=n;++i) read(s[i]);
for(int i=1;i<=m;++i) read(w[i]);
for(int i=1;i<=m;++i){
for(int j=1;j<=n;++j){
co[i][j]=s[j]*w[i];
}
}
dp[0][0]=1;
for(int i=1;i<=m;++i){
for(int j=-10000;j<=10000;++j){
for(int k=1;k<=n;++k){
dp[i][j]=dp[i-1][j-co[i][k]]+dp[i][j];
}
}
}
cout<<dp[m][0]<<endl;
};
freopen("balance.in","r",stdin);
freopen("balance.out","w",stdout);
// read(T);
while(T--) solve();
return 0;
}
T2 山峰数(hill)
题意
山峰数是指数字排列中不存在山谷(先降后升)的数,例如 0,5,13,12321 都是山峰数,101,1110000111 都不是山峰数。
现给出 n 个数,请依次判断它们是否为山峰数,如果不是,输出-1。如果是,求出比它小的数中有多少个山峰数。
思路
简单数位DP,因为要考虑之前是否下降过,所以在转移过程过添加一维 \(flag\) 表示之前是否已经下降过了,然后还需要一维前缀 \(pre\)。
填数字的时候只需要判断一下是否 \(flag\) 并且当前填的数比前缀 \(pre\) 大,如果是,则跳过,否则搜索。
如果当前是下降的,把 \(flag\) 设为 \(1\) 即可,其余情况不变。
其实数位DP用记忆化搜索好理解多了。
代码
#include<bits/stdc++.h>
#define int long long
#define ull unsigned long long
#define PII pair<int,int>
#define mk(a,b) make_pair(a,b)
using namespace std;
template<typename P>
inline void read(P &x){
P res=0,f=1;
char ch=getchar();
while(ch<'0' || ch>'9'){
if(ch=='-') f=-1;
ch=getchar();
}
while(ch>='0' && ch<='9'){
res=(res<<3)+(res<<1)+(ch^48);
ch=getchar();
}
x=res*f;
}
template<typename PP>
inline void write(PP x){
if(x<0) putchar('-'),x=-x;
if(x>=10) write(x/10);
putchar('0'+x%10);
}
int T=1;
char a[72];
int n;
int num[72];
int f[72][72][2];
int dfs(int now,bool op0,bool lim,int pre,bool flag){
if(!now) return 1;
if(!op0 && !lim && f[now][pre][flag]!=-1) return f[now][pre][flag];
int up=lim?num[now]:9,res=0;
for(int i=0;i<=up;++i){
if(flag && i<=pre) res+=dfs(now-1,(op0 && i==0),(lim && i==up),i,1);
else if(!flag) res+=dfs(now-1,(op0 && i==0),(lim && i==up),i,(i<pre));
}
if(!op0 && !lim) f[now][pre][flag]=res;
return res;
}
signed main(){
memset(f,-1,sizeof(f));
auto solve=[&](){
cin>>(a+1);
n=strlen(a+1);
bool down=0;
for(int i=1;i<=n;++i) num[i]=a[i]-'0';
for(int i=1;i<n;++i){
if(num[i]<num[i+1] && down){
cout<<-1<<endl;
return;
}
if(num[i]>num[i+1]) down=1;
}
reverse(num+1,num+n+1);
cout<<dfs(n,1,1,0,0)-1<<endl;
};
freopen("hill.in","r",stdin);
freopen("hill.out","w",stdout);
read(T);
while(T--) solve();
return 0;
}
T3 粉刷匠 2(draw)
题意
\(4 \times n\) 的矩阵,有 256 种颜色,每个位置都可以选择一种颜色。
现在要满足以下条件:
- \(A(x,y) \ge A(x,y-1)\)
- 有一些指定的 \((x1,y1)\) 和 \((x2,y2)\),要求 \(A(x1,y1)=A(x2,y2)\)
求方案数,只输出答案后 5 位。
思路
比较有意思的背包。
我们不按照常规思维枚举行列,而是从小到大枚举颜色。若当前枚举到的颜色为 \(i\),我们使用 \(dp[l1][l2][l3][l4]\) 表示每一行使用当前颜色涂到哪一个位置。
假设有一行现在是 123344556,现在涂 7,显然涂 7 只能涂序列的后缀,比如涂成 123777777,或者 123344577 才能符合条件。所以考虑枚举当前颜色涂到哪个后缀来转移,转移时同样需要使用完全背包的降维思想优化。
对于限制条件,我们需要把不符合限制条件的去掉,什么样的方案符合限制条件呢?对于限制条件 \(A(x1,y1) = A(x2,y2)\),和当前枚举到的颜色 \(i\),要么 \(x1\) 行和 \(x2\) 行,当前颜色都涂到了 \(y1,y2\) 位置,要么都没有涂到 \(y1,\) 位置。除此之外的都是不合法的方案,我们事先处理好一个 \(vis\) 数组,\(vis[l1][l2][l3][l4]\) 表示四行分别涂到 \(l1,l2,l3,l4\),是否可行,在 dp 时如果遇到标记不可行的 \(vis\),这 dp 值设为 \(0\) 即可。
代码
#include<bits/stdc++.h>
#define ll long long
#define ull unsigned long long
#define PII pair<int,int>
#define mk(a,b) make_pair(a,b)
using namespace std;
template<typename P>
inline void read(P &x){
P res=0,f=1;
char ch=getchar();
while(ch<'0' || ch>'9'){
if(ch=='-') f=-1;
ch=getchar();
}
while(ch>='0' && ch<='9'){
res=(res<<3)+(res<<1)+(ch^48);
ch=getchar();
}
x=res*f;
}
template<typename PP>
inline void write(PP x){
if(x<0) putchar('-'),x=-x;
if(x>=10) write(x/10);
putchar('0'+x%10);
}
int T=1;
const int mod=100000;
int n,m,tc=1;
int c[4];
int f[16][16][16][16];
bool vis[16][16][16][16];
int r1x[105],r2x[105],r1y[105],r2y[105];
signed main(){
auto solve=[&](){
for(int tt=1;tt<=tc;++tt){
memset(vis,0,sizeof(vis));
read(n),read(m);
for(int i=1;i<=m;++i){
read(r1x[i]),read(r1y[i]),read(r2x[i]),read(r2y[i]);
r1x[i]--,r2x[i]--;
}
for(int i=1;i<=m;++i){
for(c[0]=0;c[0]<=n;++c[0])
for(c[1]=0;c[1]<=n;++c[1])
for(c[2]=0;c[2]<=n;++c[2])
for(c[3]=0;c[3]<=n;++c[3])
if(c[r1x[i]]>=r1y[i]^c[r2x[i]]>=r2y[i])
vis[c[0]][c[1]][c[2]][c[3]]=1;
}
memset(f,0,sizeof(f));
f[0][0][0][0]=1;
for(int col=0;col<=255;++col){
for(int cc=0;cc<=3;++cc)
for(c[0]=0;c[0]<=n;++c[0])
for(c[1]=0;c[1]<=n;++c[1])
for(c[2]=0;c[2]<=n;++c[2])
for(c[3]=0;c[3]<=n;++c[3])
if(c[cc]<n){
int tmp=f[c[0]][c[1]][c[2]][c[3]];
c[cc]++;
f[c[0]][c[1]][c[2]][c[3]]=(f[c[0]][c[1]][c[2]][c[3]]+tmp)%mod;
c[cc]--;
}
for(c[0]=0;c[0]<=n;++c[0])
for(c[1]=0;c[1]<=n;++c[1])
for(c[2]=0;c[2]<=n;++c[2])
for(c[3]=0;c[3]<=n;++c[3])
if(vis[c[0]][c[1]][c[2]][c[3]]) f[c[0]][c[1]][c[2]][c[3]]=0;
}
printf("%05d\n",f[n][n][n][n]);
}
};
// freopen("draw.in","r",stdin);
// freopen("draw.out","w",stdout);
//read(T);
while(T--) solve();
return 0;
}
T4 棋盘(knight)
题意
有一个 \(N \times M\) 的棋盘,要在上面摆上 knight,每个格子可以放至多一个knight。
knight 的攻击范围为国际象棋中马的移动范围。
所有 knight 不能互相攻击,请问总共有多少可行的摆法?答案对 1000000007 取模。
思路
状压DP
考虑压当前行,上一行,上两行三个状态。
转移还是正常转移,但是这样你会发现你寄了。
这个时候我们发现可以使用矩阵优化。
咕咕咕
NOIP 模拟考
多校联测(2024.10.23)
T1 tree
题意
思路
构造题,如果注意力十分集中,能够注意到,菊花图带链(蒲公英)是近似于正确的答案,但发现取中间时会错,那可以构造两条链,即两条链的菊花图即可
代码
#include<bits/stdc++.h>
#define ll long long
using namespace std;
template<typename P>
void read(P &x){
P res=0,f=1;
char ch=getchar();
while(ch<'0' || ch>'9'){
if(ch=='-') f=-1;
ch=getchar();
}
while(ch>='0' && ch<='9'){
res=(res<<3)+(res<<1)+ch-'0';
ch=getchar();
}
x=res*f;
}
int T=1;
int tp,n,x;
void solve(){
cin>>tp>>n>>x;
if((x<2 || x>n/2+1) && (n!=1 || x!=1)){
cout<<"No"<<'\n';
return;
}
else cout<<"Yes"<<'\n';
if(tp==0 || n==1) return;
int t=n/2+1,tt=t-x+1;
for(int i=1;i<=tt;++i) cout<<i<<' '<<tt+1<<'\n';
for(int i=tt+1;i<n;++i) cout<<i<<' '<<i+1<<'\n';
return;
}
signed main(){
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
freopen("tree.in","r",stdin);
freopen("tree.out","w",stdout);
cin>>T;
while(T--) solve();
return 0;
}
T2 suffix
题意
思路
我们发现一个满足条件的区间,最后一个必定与整个字符串最后一个相同,即
这样我们只需要考虑以 \(S_n\) 结尾的区间 \([l,r]\),然后发现我们可以先求最长公共后缀能抵达的位置,记为 \(f_i\),这个的意义就是求与后缀的 \(boader\) 的反序。
图中的每个箭头的终点表示的是初始时 \(f[r]\) 的的值,我们发现这个箭头具有传递性,意思是如果 \(f[i]>f[i-1]\) 那么这个区间必定可以分成 \([f_{i-1},i-1]\) 和 \([i-1,i]\),然后这个东西相当于在 \([f_i,i]\) 求最小值,可以用线段树处理,至于前面的预先求出最长 \(border\),可以用二分 Hash 或者倍增,exKMP都可以处理。
简洁一点:
- 求当前位置的最长后缀能达到的位置 \(F_i\)
- 对 \([F_i,i]\) 求 \(F_i\) 最小值(传递性),这就是以 \(i\) 结尾的最长的能被分成若干个后缀的区间的左端点位置。
- 那么询问就是判断是否 \(F_r<=l\) 即可。
代码
#include<bits/stdc++.h>
#define endl "\n"
#define int long long
using namespace std;
template<typename P>
void read(P &x){
P res=0,f=1;
char ch=getchar();
while(ch<'0' || ch>'9'){
if(ch=='-') f=-1;
ch=getchar();
}
while(isdigit(ch)){
res=(res<<3)+(res<<1)+ch-'0';
ch=getchar();
}
x=res*f;
}
int T=1;
const int N=1e6+10,mod=1e9+7,base=1e6+7;
const int INF=0x3f3f3f3f3f3f3f3f;
int n,q;
char s[N];
int nxt[N],f[N];
int h[N],be[N];
int query(int l,int r){
return (h[r]-h[l-1]*be[r-l+1]%mod+mod)%mod;
}
struct Stree{
#define lson (rt<<1)
#define rson (rt<<1|1)
int t[N<<2];
void pushup(int rt){
t[rt]=min(t[lson],t[rson]);
}
void build(int rt,int l,int r){
if(l==r) {t[rt]=INF;return;}
int mid=(l+r)>>1;
build(lson,l,mid);
build(rson,mid+1,r);
pushup(rt);
}
void update(int rt,int l,int r,int k,int x){
if(l==r) {t[rt]=x;return;}
int mid=(l+r)>>1;
if(k<=mid) update(lson,l,mid,k,x);
else update(rson,mid+1,r,k,x);
pushup(rt);
}
int query(int rt,int l,int r,int L,int R){
if(L<=l && r<=R) return t[rt];
int mid=(l+r)>>1,res=INF;
if(L<=mid) res=min(res,query(lson,l,mid,L,R));
if(R>mid) res=min(res,query(rson,mid+1,r,L,R));
return res;
}
}Q;
signed main(){
auto solve=[&](){
read(n),read(q);
scanf("%s",s+1);
be[0]=1;
for(int i=1;i<=n;++i){
h[i]=(h[i-1]*base%mod+(s[i]-'a'+1))%mod;
be[i]=be[i-1]*base%mod;
}
Q.build(1,1,n);
char op=s[n];
memset(f,0x3f,sizeof(f));
for(int i=1;i<=n;++i){
if(s[i]==op){
int l=1,r=i,ans=0;
while(l<=r){
int mid=(l+r)>>1;
if(query(i-mid+1,i)==query(n-mid+1,n)) l=mid+1,ans=mid;
else r=mid-1;
}
f[i]=i-ans+1;
Q.update(1,1,n,i,f[i]);
f[i]=Q.query(1,1,n,max(f[i]-1,1ll),i);
Q.update(1,1,n,i,f[i]);
}
}
while(q--){
int l,r;
read(l),read(r);
int id=f[r];
if(id<=l) cout<<"Yes"<<endl;
else cout<<"No"<<endl;
}
return;
};
freopen("suffix.in","r",stdin);
freopen("suffix.out","w",stdout);
// read(T);
while(T--) solve();
return 0;
}
T3 subsequence
题意
思路
发现对于第二个性质,我们 \(ln\) 一下发现有 \(b \ln a < a \ln b\),即 \(\dfrac{a}{\ln a}>\dfrac{b}{\ln b}\)。
考虑函数 \(f(x)=\dfrac{x}{\ln x}\),求导有 \(f'(x)=\ln(x)^{-1}-\ln(x)^{-2}\)。不难看出 \(f'(x)\) 有唯一零点 \((e,0)\),故 \(f(x)\) 在 \(x=e\) 时取得最小值。
所以在 \(i<j\) 且 \(i>e\) 时,总有 \(b_{i}^{b_{j}} < b_{j}^{b_{i}}\)。
其实看不懂没关系,只需要打表观察发现序列 \(B\) 的种类其实很少,分下面 3 类:
-
序列无 \(1\):则顺序对至多为 \(1\),无合法序列。
-
序列有 \(1\),不同时存在 \(2,3\):此时记序列长度为 \(n\),则顺序对数为 \(n-1\),逆序对数为 \(\dfrac{(n-1)(n-2)}{2}\)。联立解得 \(n=4\),故此时子序列呈 \(\{1 , a, b, c\}(a>b>c)\)。
-
序列有 \(1\),同时存在 \(2,3\),同理解方程,可知此时子序列呈 \(\{1 , a, b, 2, 3\}(a>b \ne 4)\)。
所以序列的最大长度暂且为 \(4\),第三种情况我们先不管。那么有贡献的必定为 \(B_2,B_3\),那么我们可以开两个值域线段树存的是 \(B_2,B_3\) 的数量。
每次碰到一个不是 \(2\) 的,那么将它加进第一颗线段树,统计线段树内它后面的元素个数总和,将这个总和加进第二颗线段树,再对第二颗线段树内它后面的元素个数求和累加进 \(ans\)。如果碰到 \(2\),\(ans\) 还是要累加答案的,但同时累加到 \(ans2\) 中,因为如果 \(B\) 序列是 \(\{1,9,8,2,3\}\),那么还是有贡献的,单独处理一下即可。
代码
#include<bits/stdc++.h>
#define ll long long
#define ull unsigned long long
#define PII pair<int,int>
#define mk(a,b) make_pair(a,b)
using namespace std;
template<typename P>
inline void read(P &x){
P res=0,f=1;
char ch=getchar();
while(ch<'0' || ch>'9'){
if(ch=='-') f=-1;
ch=getchar();
}
while(ch>='0' && ch<='9'){
res=(res<<3)+(res<<1)+(ch^48);
ch=getchar();
}
x=res*f;
}
template<typename PP>
inline void write(PP x){
if(x<0) putchar('-'),x=-x;
if(x>=10) write(x/10);
putchar('0'+x%10);
}
int T=1;
const int N=1e6+10,mod=998244353;
int n;
int a[N];
struct Stree{
#define lson (rt<<1)
#define rson (rt<<1|1)
int t[N<<2];
void pushup(int rt) {t[rt]=t[lson]+t[rson];t[rt]%=mod;}
void update(int rt,int l,int r,int k,int x){
if(l==r) {t[rt]+=x,t[rt]%=mod;return;}
int mid=(l+r)>>1;
if(k<=mid) update(lson,l,mid,k,x);
else update(rson,mid+1,r,k,x);
pushup(rt);
}
int query(int rt,int l,int r,int L,int R){
if(L<=l && r<=R) return t[rt];
int mid=(l+r)>>1,res=0;
if(L<=mid) res+=query(lson,l,mid,L,R),res%=mod;
if(R>mid) res+=query(rson,mid+1,r,L,R),res%=mod;
return res%mod;
}
}Q1,Q2;
signed main(){
auto solve=[&](){
read(n);
for(int i=1;i<=n;++i) read(a[i]);
int f1=0;
int ans=0,ans2=0;
for(int i=1;i<=n;++i){
if(a[i]==1) {f1++;continue;}
if(f1==0) continue;
Q1.update(1,1,n,a[i],f1);
int tot=Q1.query(1,1,n,a[i]+1,n);
Q2.update(1,1,n,a[i],tot);
if(a[i]==2) ans+=Q2.query(1,1,n,5,n),ans2+=Q2.query(1,1,n,5,n),ans%=mod,ans2%=mod;
else ans+=Q2.query(1,1,n,a[i]+1,n),ans%=mod;
if(a[i]==3) ans+=ans2,ans%=mod;
}
cout<<(ans+n)%mod<<endl;
};
freopen("subsequence.in","r",stdin);
freopen("subsequence.out","w",stdout);
//read(T);
while(T--) solve();
return 0;
}
CSP-S(2024.10.24)
T1 queue
题意
思路
太唐了,直接写。
代码
#include<bits/stdc++.h>
#define endl "\n"
#define ll long long
using namespace std;
template<typename P>
void read(P &x){
P res=0,f=1;
char ch=getchar();
while(!isdigit(ch)){
if(ch=='-') f=-1;
ch=getchar();
}
while(isdigit(ch)){
res=(res<<3)+(res<<1)+ch-'0';
ch=getchar();
}
x=f*res;
}
int T=1;
int n;
void solve(){
read(n);
if(n%2==0){
for(int i=n;i>=1;i-=2) printf("%d ",i);
for(int i=1;i<=n;i+=2) printf("%d ",i);
}
else{
for(int i=n;i>=1;i-=2) printf("%d ",i);
for(int i=2;i<=n;i+=2) printf("%d ",i);
}
puts("");
return;
}
signed main(){
freopen("queue.in","r",stdin);
freopen("queue.out","w",stdout);
// read(T);
while(T--) solve();
return 0;
}
T2 cutin
题意
思路
本来是想做DP的,但是发现一些性质:
- 对于 \([m+1,n]\) 的队伍,无论怎么走反正都是要走完的,然后我们可以发现,在某个位置停下相当于选了这个位置的 \(a_i\),如果不停下就是选了 \(b_i\),那么这一段相当于就是选 \(a_i\) 和 \(b_i\) 的较小值即可。
- 对于 \([1,m]\) 的队伍,最多只会停一次(因为已经满足要求了就不用走了),我们考虑在哪儿停下,发现在 \(i\) 停下的花费是 \(a_i+\sum\limits_{j=i}^{m}b_j\),后面的求和用前缀和处理即可,求一个最小值就行。
PS:其实DP也能做。
代码
#include<bits/stdc++.h>
#define endl "\n"
#define int long long
using namespace std;
template<typename P>
void read(P &x){
P res=0,f=1;
char ch=getchar();
while(!isdigit(ch)){
if(ch=='-') f=-1;
ch=getchar();
}
while(isdigit(ch)){
res=(res<<3)+(res<<1)+ch-'0';
ch=getchar();
}
x=f*res;
}
int T=1;
const int N=2e5+10;
int n,m;
int a[N],b[N];
int sumb[N],sum[N];
void solve(){
read(n),read(m);
for(int i=1;i<=n;++i) read(a[i]);
for(int i=1;i<=n;++i) read(b[i]),sumb[i]=sumb[i-1]+b[i];
int ans=0;
for(int i=n;i>m;--i){
if(a[i]>b[i]) ans+=b[i];
else ans+=a[i];
}
int sum=0x3f3f3f3f;
for(int i=1;i<=m;++i){
sum=min(sum,a[i]+sumb[m]-sumb[i]);
}
printf("%lld\n",ans+sum);
return;
}
signed main(){
freopen("cutin.in","r",stdin);
freopen("cutin.out","w",stdout);
read(T);
while(T--) solve();
return 0;
}
T3 number
题意
思路
一眼数位DP,但是写完后发现需要存每个数字,\(1e18\) 的数据根本不行,我们换一种方法。
我们发现一个数取模 \(2520\)(\([1,9]\)所有数字的 \(\operatorname {lcm}\))后再考虑也是等价的,即
那么现在就可以从 \(1e18\) 压缩到 \(2520\) 了,剩下的还需要存一下现在选的数字的 \(\gcd\),以及 \(\operatorname {lcm}\)(这二者可以使用状压维护每个数字)最后就是正常数位DP了。
代码
#include<bits/stdc++.h>
#define endl "\n"
#define int long long
using namespace std;
template<typename P>
void read(P &x){
P res=0,f=1;
char ch=getchar();
while(!isdigit(ch)){
if(ch=='-') f=-1;
ch=getchar();
}
while(isdigit(ch)){
res=(res<<3)+(res<<1)+ch-'0';
ch=getchar();
}
x=f*res;
}
int T=1;
int num[20];
int Gcd[3005];
int to=0;
int f[20][2601][51];
int gcd(int a,int b){
if(!b) return a;
return gcd(b,a%b);
}
int lc(int x,int y){
return x*y/gcd(x,y);
}
int dfs(int now,int op0,int lim,int sum,int yu){
if(!now) return (yu%sum==0);
if(!lim && !op0 && f[now][yu][Gcd[sum]]!=-1) return f[now][yu][Gcd[sum]];
int up=(lim)?num[now]:9,res=0;
for(int i=0;i<=up;++i){
if(i==0 && op0!=1) continue;
res+=dfs(now-1,(op0 && i==0),(lim && i==up),(i?lc(sum,i):sum),(yu*10+i)%2520);
}
if(!lim && !op0) f[now][yu][Gcd[sum]]=res;
return res;
}
int work(int x){
int len=0;
memset(f,-1,sizeof(f));
while(x){
num[++len]=x%10;
x/=10;
}
return dfs(len,1,1,1,0);
}
void solve(){
int l,r;
read(l),read(r);
for(int i=1;i<=2520;++i){
if(!(2520%i)) Gcd[i]=(++to);
}
cout<<work(r)-work(l-1)<<endl;
return;
}
signed main(){
freopen("number.in","r",stdin);
freopen("number.out","w",stdout);
// read(T);
while(T--) solve();
return 0;
}
多校联测(2024.11.20)
T1 a
题意
P9827 [ICPC2020 Shanghai R] Sky Garden
思路
大致意思就是求若干个圆心为 \((0,0)\) 的同心圆与若干条过圆心的直线的交点之间的最短路之和。
发现可以分开讨论走圆弧和走直线的路径。
由于圆弧是等分的,所以一个点要走圆弧的相邻路径可以容易得出,剩下的就是算某个点到其他点走直线所花的的距离。
luogu上的复杂度可以 \(O(N^3)\),赛时是加强版,只能 \(O(N)\) 及以下,乱搞一下就ok了。
代码
#include<bits/stdc++.h>
#define endl '\n'
#define int long long
#define ull unsigned long long
#define PII pair<int,int>
#define mk(a,b) make_pair(a,b)
using namespace std;
template<typename P>
inline void read(P &x){
P res=0,f=1;
char ch=getchar();
while(ch<'0' || ch>'9'){
if(ch=='-') f=-1;
ch=getchar();
}
while(ch>='0' && ch<='9'){
res=(res<<3)+(res<<1)+(ch^48);
ch=getchar();
}
x=res*f;
}
template<typename Ty,typename ...Args>
inline void read(Ty &x,Args &...args) {read(x);read(args...);}
int T=1;
const int mod=998244353;
const double pi=acos(-1);
int n,m;
inline void solve(){
read(n,m);
int cnt=0,ans1=0,ans2=0;
for(int i=1;i<=m;++i) if(1.0*pi/m*i>2.0) {cnt=i-1;break;}
int num=(cnt+1)*cnt/2%mod;
for(int i=1;i<=n;++i){
ans1+=i*num%mod*2%mod+(n-i)*2%mod*num%mod*i%mod*2%mod;
ans1%=mod;
}
for(int i=1;i<=n;++i){
ans2=(ans2+(n-i+1)*(n-i)%mod*m%mod+2*m%mod*i%mod+(n-i+1)*(n-i)%mod*cnt%mod*m%mod*2%mod)%mod;
int p=((n+1)*n/2%mod+n*i%mod)%mod;
ans2=(ans2+p*((m-cnt)*2-1)%mod*m%mod)%mod;
}
cout<<(ans1+mod+mod)%mod<<' '<<(ans2+mod+mod)%mod<<endl;
}
signed main(){
freopen("a.in","r",stdin);
freopen("a.out","w",stdout);
//read(T);
while(T--) solve();
return 0;
}
剩下的不会。咕咕咕。