The 2024 CCPC National Invitational Contest (Northeast), The 18th Northeast Collegiate Programming Contest 2024 CCPC 东北赛
当时还打过这场比赛,和 yhp cpy。当时啥也不会,只做了三题遗憾打铁,哎 现在 VP 发现当时不会的题好简单
J - Breakfast
纯签
点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
using pii=pair<int,int>;
using ll = long long;
using ull = unsigned long long;
const ll inf = 1e18;
const int mod = 998244353;
void solve(){
double n,m;
cin>>n>>m;
double ans=n*0.6+m*1;
printf("%.2lf",ans);
}
signed main(){
// ios::sync_with_stdio(0);
// cin.tie(0);
int ct=1;
// cin>>ct;
while(ct--){
solve();
}
}
D - nIM gAME
手玩一下样例就会发现,全输
点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
using pii=pair<int,int>;
using ll = long long;
using ull = unsigned long long;
const ll inf = 1e18;
const int mod = 998244353;
void solve(){
int n;
cin>>n;
cout<<"lose\n";
}
signed main(){
ios::sync_with_stdio(0);
cin.tie(0);
int ct=1;
cin>>ct;
while(ct--){
solve();
}
}
A - Paper Watering
先变大再变小一定是不优的,所以只考虑先变小后变大的情况。
如果每次开根号时,发生了下取整,则一定不可能再变成之前的数。对于当前这个数及其多次平方变成的数,全都是新的
否则没有发生下取整,则其平方变成的数一定被之前的数覆盖。只有当前数本身是一个新数
注意特判变成 1 或一开始就是 1 的情况
点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
using pii=pair<int,int>;
using ll = long long;
using ull = unsigned long long;
const ll inf = 1e18;
const int mod = 998244353;
void solve(){
int n,k;
cin>>n>>k;
int ans=k+1;
int cnt=0;
if(n==1){
cout<<1;
return;
}
while(n>1 && k--){
int ne=sqrt(n);
ans++;
if(ne*ne!=n && ne!=1){
ans+=k;
}
n=ne;
}
cout<<ans;
}
signed main(){
ios::sync_with_stdio(0);
cin.tie(0);
int ct=1;
// cin>>ct;
while(ct--){
solve();
}
}
E - Checksum
因为 \(k\) 最大是 \(20\),所以直接枚举,后面的二进制表示有几个 \(1\) 就可以了
点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
using pii=pair<int,int>;
using ll = long long;
using ull = unsigned long long;
const ll inf = 1e18;
const int mod = 998244353;
int check(int val){
for(int i=60;i>=0;i--){
if((val>>i) & 1) return i+1;
}
return 0;
}
void solve(){
int n,k;
cin>>n>>k;
int ans=-1;
string s;
cin>>s;
int cnt=0;
for(auto ch:s){
cnt+=(ch=='1');
}
for(int i=0;i<=k;i++){
int now=cnt+i;
if(__builtin_popcount(now)==i && check(now)<=k){
ans=now;
break;
}
}
if(ans==-1){
cout<<"None\n";
return;
}
vector<int> stk;
while(ans){
if(ans&1) stk.push_back(1);
else stk.push_back(0);
ans>>=1;
}
int nd=k-stk.size();
while(nd--){
cout<<0;
}
while(stk.size()){
cout<<stk.back();
stk.pop_back();
}
cout<<endl;
}
signed main(){
ios::sync_with_stdio(0);
cin.tie(0);
int ct=1;
cin>>ct;
while(ct--){
solve();
}
}
L - Bracket Generation
首先,操作一 () 之间的相互顺序是不能动的
其次,一个 () 后面跟的操作二,是不能比这个 () 早出现的,但可以晚出现
可以从左往右扫,遇到 () 就记为 1,遇到 )) 这样的,就是操作 2,记为 2
得到序列 11211212
根据上面的分析,1 不能乱动,但是 2 可以往后挪。找这种方案数量
等价于对于每个 2 ,对答案累乘 n-i+1
点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
using pii=pair<int,int>;
using ll = long long;
using ull = unsigned long long;
const ll inf = 1e18;
const int mod = 998244353;
void solve(){
string s;
cin>>s;
vector<int> a(1);
for(int i=1;i<s.size();i++){
if(s[i]==')'){
if(s[i-1]=='(') a.push_back(1);
else a.push_back(2);
}
}
int ans=1;
int n=a.size()-1;
for(int i=1;i<=n;i++){
if(a[i]==2) ans*=(n-i+1);
ans%=mod;
}
cout<<ans;
}
signed main(){
ios::sync_with_stdio(0);
cin.tie(0);
int ct=1;
// cin>>ct;
while(ct--){
solve();
}
}
F - Factor
等价于,\(p*k*k*...*k~mod~q=0\)
所以提取 \(p\) 和 \(k\) 的质因数并计数,注意 \(k\) 可以有无限个,所以其因数个数也是无限,实际取值 60 就行
然后对这些因数 DFS 可以凑出多少合法的数即可
点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
using pii=pair<int,int>;
using ll = long long;
using ull = unsigned long long;
const ll inf = 1e18;
const int mod = 998244353;
void solve(){
int p,x,k;
cin>>p>>x>>k;
map<int,int> mp;
int tmp=p;
for(int i=2;i*i<=tmp;i++){
if(tmp%i!=0) continue;
while(tmp%i==0){
mp[i]++;
tmp/=i;
}
}
if(tmp>1) mp[tmp]=1;
tmp=k;
for(int i=2;i*i<=tmp;i++){
if(tmp%i!=0) continue;
while(tmp%i==0){
tmp/=i;
}
mp[i]=60;
}
if(tmp>1) mp[tmp]=60;
vector<pii> t;
for(auto [x,y]:mp){
t.push_back({x,y});
}
int ans=0;
auto dfs=[&](auto dfs,int pos,int sum)-> void {
if(pos==t.size()){
ans++;
return;
}
int val=t[pos].first;
int cnt=t[pos].second;
for(int i=0;i<=cnt;i++){
dfs(dfs,pos+1,sum);
if((__int128_t)sum*val>x) break;
sum*=val;
}
};
dfs(dfs,0,1);
cout<<ans<<endl;
}
signed main(){
ios::sync_with_stdio(0);
cin.tie(0);
int ct=1;
// cin>>ct;
while(ct--){
solve();
}
}
I - Password
“所有位置都是好的”意味着整个序列被若干个长度为 \(k\) 的排列完全覆盖。为了保证相邻两部分没有断层,相邻两个排列起点的距离不能超过 \(k\)。同时,序列的最后 \(k\) 个数一定是一个完整的排列。
我们可以用动态规划来解决,核心在于如何避免合法序列被重复计数。
- 基础构件:连通排列(不可分解排列)
设向后扩展序列时,我们想要新增 \(j\) 个数字(\(1 \le j \le k\)),使得它与前面的后缀恰好第一次拼凑成一个新的排列。
为了保证它是“第一次”拼凑成功,这新增的 \(j\) 个数字所构成的结构,不能在内部提前形成完整的排列。这种前缀中不包含任何子排列的序列被称为不可分解排列。
设 \(g_i\) 表示长度为 \(i\) 的不可分解排列的方案数。
我们通过容斥原理计算 \(g_i\):
从全排列总数 \(i!\) 中,减去那些“不合法”(提前出现子排列)的方案。我们枚举第一次出现完整排列的前缀长度 \(i-j\)(即前 \(i-j\) 个数构成了一个不可分解排列,方案数为 \(g_{i-j}\)),剩下的 \(j\) 个数随意排列(方案数为 \(j!\))。
- 状态转移:拼接合法序列
定义 \(f_i\) 表示长度为 \(i\),且恰好以一个完整排列结尾的合法序列方案数。
我们枚举距离上一个完整排列的步数 \(j\):
从长度为 \(i-j\) 的合法序列(以排列结尾,方案数为 \(f_{i-j}\))转移过来,向后添加 \(j\) 个数字。为了防止这 \(j\) 个数字在中间某个位置提前凑成排列导致重复统计,这 \(j\) 步的方案数恰好就是我们上面求的 \(g_j\)。
因为每次最多跳 \(k\) 步,且跳完后前面的序列长度至少得是 \(k\)(必须有一个基础排列),所以步数 \(j \le \min(k, i-k)\)。
初值:\(f_k = k!\)。最终答案即为 \(f_n\)。
复杂度分析
预处理 \(g_i\):需要两层循环,复杂度 \(O(k^2)\)。
计算 \(f_i\):外层循环 \(n\),内层循环最多 \(k\),复杂度 \(O(nk)\)。
总时间复杂度:\(O(nk + k^2)\),在 \(n \le 10^5, k \le 1000\) 的数据范围内可以轻松通过。
已知当前状态的前 \(k\) 个数字已经是一个合法的排列(设为 \(P_{old}\)),我们要向后追加 \(j\) 个数字,使得这 \(j\) 个数字加上 \(P_{old}\) 的后 \(k-j\) 个数字,恰好能拼成一个新的合法排列(设为 \(P_{new}\))。
集合的必然性:
既然 \(P_{new}\) 借用了 \(P_{old}\) 的后 \(k-j\) 个数字,那么为了凑齐 \(1 \sim k\),我们新加进来的这 \(j\) 个数字,在集合层面上,必须完全等同于 \(P_{old}\) 的前 \(j\) 个数字。
提前截断的触发条件:
假设新加的 \(j\) 个数字中,取前 \(m\) 个数字(\(m < j\)),它和之前的数字提前构成了排列。这意味着什么?
这意味着这 \(m\) 个数字,恰好弥补了 \(P_{old}\) 倒数 \(k-m\) 个数字所缺失的部分——也就是这 \(m\) 个数字刚好等同于 \(P_{old}\) 的前 \(m\) 个数字。
同构映射(The Leap):
因为新加的 \(j\) 个数一定是 \(P_{old}\) 前 \(j\) 个数的某种打乱重排。
如果我们把 \(P_{old}\) 的前 \(j\) 个数,按照它们在原序列中的位置,重新离散化映射为 \(1, 2, 3, \dots, j\):
那么“新加的序列中,长度为 \(m\) 的前缀刚好是 \(P_{old}\) 的前 \(m\) 个数”等价于 “长度为 \(m\) 的前缀刚好构成了 \(1 \sim m\) 的排列”。
点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
using pii=pair<int,int>;
using ll = long long;
using ull = unsigned long long;
const ll inf = 1e18;
const int mod = 998244353;
void solve(){
int n,k;
cin>>n>>k;
if(k>n){
cout<<0;
return;
}
vector<int> g(n+1,1),f(n+1),fact(n+1,1);
for(int i=2;i<=n;i++){
fact[i]=fact[i-1]*i%mod;
}
for(int i=2;i<=k;i++){
g[i]=fact[i];
for(int j=1;j<i;j++){
g[i]-=g[j]*fact[i-j];
g[i]%=mod;
}
g[i]=(g[i]%mod+mod)%mod;
}
f[k]=fact[k];
for(int i=k+1;i<=n;i++){
for(int j=1;j<=k;j++){
if(i-j<k) break;
f[i]=(f[i]+f[i-j]*g[j])%mod;
}
}
cout<<f[n];
}
signed main(){
ios::sync_with_stdio(0);
cin.tie(0);
int ct=1;
// cin>>ct;
while(ct--){
solve();
}
}

浙公网安备 33010602011771号