省选数学专题

难度升序排序(大概吧)。

[AGC016C] +/- Rectangle

签,虽然国集作业。
贪心是显然的,不说了,这里只考虑正数如何填。

如果H×WHh×h×Ww×wHh×Ww时,此时全填1肯定不行,那么要填的就是正整数k,使得k×(H×WHh×h×Ww×w)<Hh×Ww。实测500左右即可。

点此查看代码
#include<bits/stdc++.h>
using namespace std;
#define rep(i,s,t,p) for(int i = s;i <= t;i += p)
#define drep(i,s,t,p) for(int i = s;i >= t;i -= p)
#ifdef LOCAL
auto I = freopen("in.in","r",stdin),O = freopen("out.out","w",stdout);
#else
auto I = stdin,O = stdout;
#endif
using ll = long long;using ull = unsigned long long;
using db = double;using ldb = long double;
signed main(){
cin.tie(nullptr)->sync_with_stdio(false);
int H,W,h,w;
cin>>H>>W>>h>>w;
if(H%h == 0 && W%w == 0) return cout<<"No\n",0;
cout<<"Yes\n";
rep(i,1,H,1){
rep(j,1,W,1)
if(i%h == 0 && j%w == 0) cout<<-(h*w-1)*500-1<<' ';
else cout<<500<<' ';
cout<<'\n';
}
}

[JLOI2015] 骗我呢

反射容斥板子,写详细一点当反射容斥学习笔记了。

先考虑反射容斥是什么。

前置知识

在一个二维平面内,只能向右走或向上走,从(0,0)走到(n,m)的方案数是Cn+mn的,下文中,若无特殊说明,那么P(n,m)均表示从(0,0)只能向右走或向上走走到n,m的方案数,即P(n,m)=Cn+mn

证明

发现每种合法路径必然走了n+m步,且有n步向右,m步向上。所以答案就是从n+m步中选n步为向右走的方案数,即Cn+mn

考虑如果有一条直线y=x+b不可碰撞(n+b>m)。考虑一个经典trick,就是将所有碰到这条直线的路径,从第一次碰到这条直线处翻折,那么这条路径的终点就是(mb,n+b)。举个例子(y=x+1,n=m=3)。

image

显然答案就是P(n,m)P(mb,n+b)。至于为什么不考虑碰撞两次的,因为每次翻折以后都重合了,没必要考虑。

反射容斥

已经解决了只不允许碰撞一条线的,那么如果存在两条线(A:y=x+a,B:y=x+b(b<a,n+b<m<n+a))呢?

考虑不合法的方案形如依次经过AABBABAB,显然重复经过同一条直线没有意义,所以缩成ABABAB

显然答案为总方案数AB,发现后面的东西是递推的,直接求就行了。

关于本题

xi,j<xi,j+1,可知每一行是从左到右单调递增的,由0xi,jm可知,每行一定有一个数没有被用上,那么就设fi,j表示第i行第j个数没有填过,考虑转移。由xi,j<xi1,j+1可知,fi,j=k=0j+1fi1,k,可以手动模拟证明上界为j+1,显然答案为fn+1,m,前缀和优化可以做到O(nm)

考虑优化这个柿子,会发现fi,j=fi,j1+fi1,j+1,然后将其画到二维平面上,注意这里n为纵坐标。

image

感觉这个不好看,把它拉伸一下。(图是贺的)

image

发现就是从(0,0)走到(n+m+1,n),且不碰A:y=x+1,B:y=xm2的方案数,直接套反射容斥就行了,时间复杂度O(n)

点此查看代码
#include<bits/stdc++.h>
using namespace std;
#define rep(i,s,t,p) for(int i = s;i <= t;i += p)
#define drep(i,s,t,p) for(int i = s;i >= t;i -= p)
#ifdef LOCAL
auto I = freopen("in.in","r",stdin),O = freopen("out.out","w",stdout);
#else
auto I = stdin,O = stdout;
#endif
using ll = long long;using ull = unsigned long long;
using db = double;using ldb = long double;
const int N = 3e6 + 10,MN = 3e6,mod = 1e9 + 7;
int n,m,fac[N],inv[N];
int qpow(int a,int b,int mod){
int res = 1;
for(;b;b >>= 1,a = 1ll*a*a%mod)
if(b & 1) res = 1ll*res*a%mod;
return res;
}
int Inv(int a,int mod = mod){return qpow(a,mod-2,mod);}
int C(int n,int m){if(n < 0 || m < 0 || m > n) return 0;return 1ll*fac[n]*inv[m]%mod*inv[n-m]%mod;}
void flip(int &a,int &b,int x){swap(a,b);a -= x;b += x;}
signed main(){
cin.tie(nullptr)->sync_with_stdio(false);
cin>>n>>m;
fac[0] = 1;rep(i,1,MN,1) fac[i] = 1ll*fac[i-1]*i%mod;
inv[MN] = Inv(fac[MN]);
drep(i,MN-1,0,1) inv[i] = 1ll*inv[i+1]*(i+1)%mod;
int ans = C(n*2+m+1,n);
int x = n + m + 1,y = n;
while(x >= 0 && y >= 0){
flip(x,y,1);ans -= C(x+y,x);ans += mod;ans %= mod;
flip(x,y,-(m+2));ans += C(x+y,x);ans %= mod;
}
x = n + m + 1,y = n;
while(x >= 0 && y >= 0){
flip(x,y,-(m+2));ans -= C(x+y,x);ans += mod;ans %= mod;
flip(x,y,1);ans += C(x+y,x);ans %= mod;
}
cout<<ans<<'\n';
}

[AGC049D] Convex Sequence

模拟赛原,但是当时没改。

考虑将2×aiai1+ai+1换成aiai1ai+1ai,发现这就是告诉你斜率单调递增。

假设最小值所在位置为p,那么显然有1i<p,ai<ai1,p<in,ai<ai+1

假设某一个合法方案的第一个最小值所在位置为p,那么显然可以通过以下的流程还原这个方案。

  1. a中的所有数设为C,此操作只会再最开始进行一次。
  2. 选择一个i<p,将ai,ai1,,a1加上1,2,3,,此操作可以进行任意次。
  3. 选择一个i>p,将ai,ai+1,,an加上1,2,3,,此操作可以进行任意次。

当固定p时,那么显然问题转化为:有任意多个n,1,3,6,10,,求凑出和为m的数有多少种方案。

注意p的定义为第一个最小值所在的位置,所以前面的数至少都加过一次,所以需要凑出的数实际为mp×(p1)/2

剩下的,发现就是一个完全背包问题,但是每次重新做的话复杂度是O(nmm)的。不可接受。发现这个dp是可撤销的,每次统计答案前直接撤销即可,发现每个最多会撤销m个数,每次撤销复杂度是O(m)的。所以总的时间复杂度就是O(mm)

点此查看代码
#include<bits/stdc++.h>
using namespace std;
#define rep(i,s,t,p) for(int i = s;i <= t;i += p)
#define drep(i,s,t,p) for(int i = s;i >= t;i -= p)
#ifdef LOCAL
auto I = freopen("in.in","r",stdin),O = freopen("out.out","w",stdout);
#else
auto I = stdin,O = stdout;
#endif
using ll = long long;using ull = unsigned long long;
using db = long double;using ldb = long double;
const int N = 1e5 + 10,mod = 1e9 + 7;
int n,m,f[N];
signed main(){
cin.tie(nullptr)->sync_with_stdio(false);
cin>>n>>m;f[0] = 1;
auto Add = [&](int x){rep(i,x,m,1) f[i] = (f[i] + f[i-x])%mod;};
auto Del = [&](int x){drep(i,m,x,1) f[i] = (f[i] - f[i-x] + mod)%mod;};
if(n <= m) Add(n);
for(ll i = 1;i < n && i*(i+1)/2 <= m; ++i) Add(i*(i+1)/2);
int ans = 0;
rep(i,1,n,1){
if(1ll*i*(i-1)/2 <= m) ans = (ans + f[m-i*(i-1)/2])%mod;
if(1ll*(n-i)*(n-i+1)/2 <= m) Del((n-i)*(n-i+1)/2);
if(1ll*i*(i+1)/2 <= m) Add(i*(i+1)/2);
}
cout<<ans<<'\n';
}

[ABC221H] Count Multiset

套路:多重集合转不下降序列。

先不考虑限制,设fi,j表示已经填了i个数,和为j的方案数。将多重集合转为不下降序列,那么就有两种操作。

  1. 将一个0填入,有fi,j=fi1,j
  2. 将全局加一,有fi,j=fi,ji

但是有相同的数不能出现超过m次,那么就是限制连续填入0的次数为m,那么第一种操作的贡献为k=1mfik,j,但由于不知道fi,j中有多少个0,考虑再设一个dp数组gi,j表示当前填了i个数,总和为j,且序列中不含0的方案数,那么有转移方程gi,j=fi,ji

总的转移方程就是

fi,j=fi,ji+k=1mgik,j

gi,j=fi,j1

前缀和优化可以做到O(n2)

点此查看代码
#include<bits/stdc++.h>
using namespace std;
#define rep(i,s,t,p) for(int i = s;i <= t;i += p)
#define drep(i,s,t,p) for(int i = s;i >= t;i -= p)
#ifdef LOCAL
auto I = freopen("in.in","r",stdin),O = freopen("out.out","w",stdout);
#else
auto I = stdin,O = stdout;
#endif
using ll = long long;using ull = unsigned long long;
using db = long double;using ldb = long double;
const int N = 5e3 + 10,mod = 998244353;
int n,m,f[N][N],g[N][N],sum[N][N];
signed main(){
cin.tie(nullptr)->sync_with_stdio(false);
cin>>n>>m;
rep(i,1,n,1){
if(i <= m) f[i][0] = 1;
rep(j,1,n,1){
if(j >= i) g[i][j] = f[i][j] = f[i][j-i];
sum[i][j] = (sum[i-1][j] + g[i][j])%mod;
f[i][j] = (0ll + f[i][j] + sum[i-1][j] - sum[max(0,i-m-1)][j] + mod) % mod;
}
}
rep(i,1,n,1) cout<<g[i][n]<<'\n';
}

tobecontinued

posted @   CuFeO4  阅读(35)  评论(2编辑  收藏  举报
相关博文:
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
点击右上角即可分享
微信分享提示