J Circular Billiard Table
- 题意描述:从圆盘某个边缘以与竖直方向夹αα角射入一颗小球,小球在圆盘内部沿反射规律运动,求解小球在第一次回到原点之前共发生几次碰撞
- 经过分析,我们可知小球在反射过程中圆心角始终保持不变,而我们这个圆心角θ=2αθ=2α显然,所以我们可以得知小球反射nn次回到原点,一定有n⋅θ=k⋅360n⋅θ=k⋅360,其中kk为某个正整数
- 所以我们需要求解最小的nn满足有360|nθ360|nθ
- 而要求解a|n∗ba|n∗b的最小正整数nn,我们可知n=agcd(a,b)n=agcd(a,b)
- 将数值代入上述公式后即可求解
#include<bits/stdc++.h>
using namespace std;
#define ll long long
ll T,a,b;
int prime[1000005];
int cnt=0;
vector<int> ans;
int div(int x){
for(int i=1;i<=cnt;i++){
if(x==1)break;
if(x%prime[i]){
x/=prime[i];
ans.push_back(prime[i]);
i-=1;
}
}
}
int main(){
//cout<<__gcd(50,360)<<endl;
cin>>T;
while(T--){
scanf("%lld %lld",&a,&b);
ll x=b*180;
// int i;
// for(i=1;;i++){
// if(i*a % x==0)break;
// }
// cout<<i-1<<endl;
cout<<(x/__gcd(a,x))-1<<endl;
}
return 0;
}
-D Period
- 题意描述:规定一个整数TT,如果其是字符串ss的一个periodperiod当且仅当对于任意i∈(T,|s|]i∈(T,|s|],均有s[i]=s[i−T]s[i]=s[i−T]并且有1≤T≤|s|1≤T≤|s|,我们给定一个只包含小写字母的字符串,并且给定qq个询问,每个询问将字符串的某个位置修改为#,且修改之间相互独立,求当前修改情况下字符串的periodperiod数
- 本题中所有的询问都只有字符串中的一个位置改变,针对题目内容询问处理相对容易,我们先对原字符串进行预处理,通过预处理结果与修改位置我们可以得到每次询问的答案。
- 所以本题的关键在于如何进行预处理,题目中的periodperiod自然让我们联想到kmpkmp算法,我们可以先求出原字符串的nextnext数组,再借助nextnext数组做进一步的求解
- 得到nextnext数组之后,因为原字符串中第ii位应该对应nextnext数组中的第i−1i−1位,所以我们初始化上界的值应该为j=lenj=len,随后我们借助nextnext数不断向前回溯,做j=nex[j]j=nex[j],对于nex[j]!=0nex[j]!=0,我们可以说明该字符串存在一个长度为nexnex的periodperiod
- 我们对于出现过的回溯过程中出现过的nex[j]nex[j]做统计,最后计算前缀和数组a[i]a[i]表示字符串有多少个长度在ii以内的periodperiod
- 对于当前修改位置xx的字母为#,显然长度大于等于xx的periodperiod都显然不再能够称为periodperiod,但因为periodperiod的长度是同时从队首元素和队尾元素考虑的,所以我们针对xx限制长度时也需要从队首队尾分别考虑,我们做x=min(x−1,len−x)x=min(x−1,len−x),得到的xx显然就是上限长度,直接代入数组aa就是当前询问的答案
#include<bits/stdc++.h>
using namespace std;
const int maxn = 1e6+5;
char s[maxn];
int q,len;
int nex[maxn],a[maxn];
// aabaaba
// 如何对period进行求解
void Getnext(){
int j=0,k=-1;
nex[0]=-1;
while(j<=len-1){
if(k==-1||s[j]==s[k]){
j++;
k++;
nex[j]=k;
}else{
k=nex[k]; //向前回溯
}
}
}
void prenext(){
int j=len;
while(j>0){
a[nex[j]]+=1;
j=nex[j];
}
a[0]=0;
for(int i=1;i<=len-1;i++){
a[i]+=a[i-1];
}
}
int main(){
scanf("%s",s);
len=strlen(s);
Getnext();
prenext();
scanf("%d",&q);
// 先对字符串进行预处理
// 通过数组记录来处理询问相关操作
for(int i=1;i<=len;i++){
cout<<nex[i]<<' ';
}
cout<<endl;
for(int i=1;i<=len;i++){
cout<<a[i]<<' ';
}
// cout<<nex[3]<<endl;
// cout<<nex[len-1]<<endl;
while(q--){
int x;
scanf("%d",&x);
x=min(x-1,len-x);
printf("%d\n",a[x]);
}
return 0;
}
-G Desserts
- 题意描述:有nn件物品,每件物品有a[i]a[i]件,我们需要将这nn件物品全部分给至多mm个人,每个人每种物品至多只能拿一件,求对于范围[1,m][1,m]内每种人数的分配方案数
- 朴素思想:易知,只有当分配人数大于nn种物品中最大件数时,才能完全分配完所有产品,因此方案数才不为0。此时对于第ii件产品,我们显然有Ca[i]tCta[i]种分配策略,其中tt为当前的人数,满足1≤t≤m1≤t≤m,此时我们只需要将所有产品的分配策略相乘即可得到在当前人数下的分配方案总数,时间复杂度为O(mn)O(mn),显然会发生超时,所以我们接下来需要思考如何优化时间复杂度
- 优化(本题最难想到的点):我们注意到题目中有条件∑ni=1ai≤105∑i=1nai≤105,所以我们可以知道对于nn个aiai,我们最多仅能有2∗105−−−−−−√2∗105个aiai互异(n(n−1)2≤105)(n(n−1)2≤105),我们能够使用unordered_setunordered_set记录下所有不同的a[i]a[i]及其出现次数,对于出现多次的a[i]a[i]我们使用快速幂算法处理即可,最后在不同的人数下下将unordered_setunordered_set中每个a′iai′的结果计算并相乘即可得到最终结果。
- 参考代码
#include<bits/stdc++.h>
using namespace std;
#define mod 998244353
#define ll long long
const int maxn = 5e4+5;
const int M = 1e5+5;
int n,m;
int a[maxn],cnt[maxn]; // 后者用来记录a_i相同数字出现的次数
unordered_set<int> at; // 用来记录去重之后的a数组
// 暴力枚举 O(nm) 显然超时
// 所以本题的关键在于如何快速进行计算
ll exp(ll a,ll b){
ll tmp=1;
while(b){
if(b&1)tmp=(a*tmp)%mod;
a=(a*a)%mod;
b/=2;
}
return tmp%mod;
}
ll fac[M],finv[M];
ll inv(ll x){
return exp(x,mod-2);
}
int init(){
fac[0]=1;
for(int i=1;i<=M-5;i++){
fac[i]=fac[i-1]*i%mod;
}
finv[M-5]=inv(fac[M-5]);
for(int i=M-6;i>=0;i--){
finv[i]=finv[i+1]*(i+1)%mod;
}
}
ll C(ll n,ll m){ // 快速计算
if(m>n)return 0;
return fac[n]*finv[m]%mod*finv[n-m]%mod;
}
int main(){
init();
cin>>n>>m;
int maxx=0;
for(int i=1;i<=n;i++){
cin>>a[i];
maxx=max(maxx,a[i]);
}
for(int i=1;i<=n;i++){
cnt[a[i]]++;
at.insert(a[i]);
}
for(int i=1;i<=m;i++){
if(i<maxx){ // 无法完全分配
printf("0\n");
continue;
}
ll ans=1;
for(auto x:at){
ans=(ans*exp(C(i,x),cnt[x]))%mod;
}
printf("%lld\n",ans); // 输出量较大,使用printf
}
return 0;
}
-M 810975
- 参考题解:链接
- 在完成这道题以前我们先考虑一道题目
HDU 6397
-
题意描述:给定n,m,kn,m,k,要求我们选定mm个范围在[0,n−1][0,n−1]中的数,使得这mm个数的和为kk
-
我们先在选定数无上限的条件下考察该问题,显然我们可以将kk看作kk个11,我们通过m−1m−1个隔板来将这kk个11划分为mm个数,但由于数字可以取00,而隔板法显然不能够满足数字选00的需求,此时我们可以做一个等价处理,将选取的mm个数的下限都加11,保障其不能取00,这样我们所有数的和应该为k+mk+m,所以我们使用隔板法可以知道方法数应该为Cm−1k+m−1Ck+m−1m−1
-
接下来我们考虑有选取的mm限制的情况,我们已知无限制情况下的方案总数,所以我们可以利用容斥原理来考虑有限制情况下的方案总数
-
借助容斥原理,问题转化为求解选择mm个数,其中至少有ii个数大于n−1n−1,且mm个数的和为kk的方案总数。要实现至少有ii个数大于等于nn,我们可以将不限定[0,n−1][0,n−1]区间选择出来的mm个数其中的ii个数分别加上nn,保障这些数一定满足大于nn,而此时我们选择的数据总和应该为k−n∗ik−n∗i,以保障增加之后数据之和为kk,利用我们考虑无上限情况时的方法,我们可以知道情况数应该为Cim×Cm−1k+m−1−nCmi×Ck+m−1−nm−1。
-
记有至少ii个数大于n−1n−1为的情况总数为f(i)f(i),则所求结果应当为f(0)−f(1)+f(2)−⋯+f(t)f(0)−f(1)+f(2)−⋯+f(t),其中t=min(n,k/n)t=min(n,k/n)
-
参考代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int M = 1e6+5;
const int mod = 998244353;
int T,n,m,k;
ll qsm(ll a,ll b){
ll tmp=1;
while(b){
if(b&1)tmp=(tmp*a)%mod;
a=a*a%mod;
b/=2;
}
return tmp;
}
ll fac[M],finv[M];
ll inv(ll x){
return qsm(x,mod-2);
}
void init(){
fac[0]=1;
for(int i=1;i<=M-5;i++){
fac[i]=fac[i-1]*i%mod;
}
finv[M-5]=inv(fac[M-5]);
for(int i=M-6;i>=0;i--){
finv[i]=finv[i+1]*(i+1)%mod;
}
}
ll C(ll n,ll m){ // 快速计算
if(m>n)return 0;
return fac[n]*finv[m]%mod*finv[n-m]%mod;
}
int main(){
init();
cin>>T;
while(T--){
cin>>n>>m>>k;
int f=1;
int ans=0;
for(int i=0;i<=m;i++){
int tmp=1;
if(!f)tmp=mod-1; // 1的
f^=1;
if(1ll*i*n>k+m-1)break;
ans=(1ll*ans+1ll*tmp*C(m,i)%mod*C(k+m-1-i*n,m-1)%mod);
}
}
return 0;
}
- 接下来让我们回到题目本身
CCPC Weihai -M 810975
- 题意描述:给定n,m,kn,m,k,要求构造长度为nn的0101串,其中有且仅有mm个11,且连续11段落的最大长度恰为kk,求满足条件的0101串总数
- 本题要求串中连续11段落的最大长度恰好为kk,我们此处可以使用容斥原理,考虑连续11段落上限为kk的情况和连续11段落上限为k−1k−1的情况,并利用前者减去后者即可
- 所以接下来我们考虑的问题即是求满足长度为nn,11个数为mm,且连续11段落上限为kk的0101串数目
- 本题中的00我们可以视为隔板,利用00来将11划分为不同的连续段落,而因连续11段落的上限为kk,所以我们可以知道任意两个00之间11的数量的取值范围应该为[0,k][0,k],并且我们可以知道所有连续11段落的和应该为mm
- 如此我们可以将问题转化为选取n−m+1n−m+1个数,要求这n−m+1n−m+1个数的取值范围在[0,k][0,k],并且总和为nn
- 这样本题求解的问题可以直接类比到HDU 6397当中,结合其中的分析过程我们可以得到的代码
- 当然需要注意上面题目中的取值界限nn要求不能取到而本题中取值界限kk要求可以取到,需要做适当的转化
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int mod = 998244353;
const int M = 1e5+5;
// 有m个1,n-m个0,求最大1连续长度恰好为5的01串个数
// 7个1 2个0 长度限定5
//
ll qsm(ll a,ll b){
ll tmp=1;
while(b){
if(b&1)tmp=(tmp*a)%mod;
a=a*a%mod;
b/=2;
}
return tmp;
}
ll fac[M],finv[M];
ll inv(ll x){
return qsm(x,mod-2);
}
void init(){
fac[0]=1;
for(int i=1;i<=M-5;i++){
fac[i]=fac[i-1]*i%mod;
}
finv[M-5]=inv(fac[M-5]);
for(int i=M-6;i>=0;i--){
finv[i]=finv[i+1]*(i+1)%mod;
}
}
ll C(ll n,ll m){ // 快速计算
if(m>n)return 0;
return fac[n]*finv[m]%mod*finv[n-m]%mod;
}
ll cal(ll num,ll sum,ll up){
int f=1;
ll ans=0;
for(int i=0;i<=num;i++){
int tmp=1;
if(!f)tmp=mod-1; // 用于做减法运算
f^=1;
if(i*up>sum+num-1)break;
ans=(ans+tmp*C(num,i)%mod*C(sum+num-1-i*up,num-1)%mod)%mod;
//cout<<ans<<endl;
}
return ans;
}
int main(){
init();
int n,m,k;
cin>>n>>m>>k;
cout<<(cal(n-m+1,m,k+1)-cal(n-m+1,m,k)+mod)%mod<<endl;
//cout<<cal(n-m+1,m,k)<<endl;
//cout<<cal(m,n-m+1,k)<<endl;
return 0;
}
// 靠左、中间、靠右
// 任何连续1段落必须包含在两个0之间 或者 边缘与1个0之间
// 我们可以在零一串的首位加上两个哨兵0,则所有的连续1段落都在0之中
// 原题中存在 n-m个0 ,则可以存在n-m+1个1段落
// 将m个1在这n-m+1个段落中进行分配
//