Loading

考试总结

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\)

那么就能得到:

\[dp_{i,j}=dp_{i-1,j-w_i \times p_j}+dp_{i,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 种颜色,每个位置都可以选择一种颜色。

现在要满足以下条件:

  1. \(A(x,y) \ge A(x,y-1)\)
  2. 有一些指定的 \((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

题意

img

思路

构造题,如果注意力十分集中,能够注意到,菊花图带链(蒲公英)是近似于正确的答案,但发现取中间时会错,那可以构造两条链,即两条链的菊花图即可

代码
#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

题意

img

思路

我们发现一个满足条件的区间,最后一个必定与整个字符串最后一个相同,即

\[S_r=S_n \]

这样我们只需要考虑以 \(S_n\) 结尾的区间 \([l,r]\),然后发现我们可以先求最长公共后缀能抵达的位置,记为 \(f_i\),这个的意义就是求与后缀的 \(boader\) 的反序。

img

图中的每个箭头的终点表示的是初始时 \(f[r]\) 的的值,我们发现这个箭头具有传递性,意思是如果 \(f[i]>f[i-1]\) 那么这个区间必定可以分成 \([f_{i-1},i-1]\)\([i-1,i]\),然后这个东西相当于在 \([f_i,i]\) 求最小值,可以用线段树处理,至于前面的预先求出最长 \(border\),可以用二分 Hash 或者倍增,exKMP都可以处理。

简洁一点:

  1. 求当前位置的最长后缀能达到的位置 \(F_i\)
  2. \([F_i,i]\)\(F_i\) 最小值(传递性),这就是以 \(i\) 结尾的最长的能被分成若干个后缀的区间的左端点位置。
  3. 那么询问就是判断是否 \(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

题意

img

思路

发现对于第二个性质,我们 \(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. 序列有 \(1\),不同时存在 \(2,3\):此时记序列长度为 \(n\),则顺序对数为 \(n-1\),逆序对数为 \(\dfrac{(n-1)(n-2)}{2}\)。联立解得 \(n=4\),故此时子序列呈 \(\{1 , a, b, c\}(a>b>c)\)

  3. 序列有 \(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的,但是发现一些性质:

  1. 对于 \([m+1,n]\) 的队伍,无论怎么走反正都是要走完的,然后我们可以发现,在某个位置停下相当于选了这个位置的 \(a_i\),如果不停下就是选了 \(b_i\),那么这一段相当于就是选 \(a_i\)\(b_i\) 的较小值即可。
  2. 对于 \([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}\))后再考虑也是等价的,即

\[x \bmod y=x \bmod y \bmod z \ (y|z) \]

那么现在就可以从 \(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

img

思路

大致意思就是求若干个圆心为 \((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;
}

剩下的不会。咕咕咕。

posted @ 2024-09-18 15:50  God_Max_Me  阅读(24)  评论(1编辑  收藏  举报