2024 National Invitational of CCPC (Zhengzhou), 2024 CCPC Henan Provincial Collegiate Programming Contest 2024 ccpc 全国邀请赛(郑州) 10/13
25郑州邀请赛报名前一天vp了一下,7题659罚时,下班太晚了不想接着写了,剩下的时间应该还能开一题。
第二天10点报名,开始报名后11秒名额就抢光了,网卡了一下,报名顺序很靠后,直接被reject。
下午六点左右,递补名额ac
补题10/13
Problem F. 优秀字符串
签到,直接模拟即可
点击查看代码
#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;
//using i128 = __int128_t;
const ll inf = 1e18;
const int mod = 998244353;
void solve(){
int n,ans=0;
cin>>n;
while(n--){
string s;
cin>>s;
if(s.size()!=5) continue;
if(s[2]!=s[4]) continue;
set<char> st;
for(int i=0;i<4;i++){
st.insert(s[i]);
}
if(st.size()==4) ans++;
}
cout<<ans;
}
signed main(){
ios::sync_with_stdio(0);
cin.tie(0);
int ct=1;
// cin>>ct;
while(ct--){
solve();
}
return 0;
}
B. 扫雷 1
一眼反悔贪心,但wa了三发,读错题wa一发,细节wa两发
从前往后能买就买,同时把买单个价格和购买个数放进大根堆里。
当在后面遇到一个价格比较小的,则退掉前面已经买了且单价比当前这个大的,把钱全买这个
点击查看代码
#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;
//using i128 = __int128_t;
const ll inf = 1e18;
const int mod = 998244353;
void solve(){
int n;
cin>>n;
vector<int> a(n+1);
for(int i=1;i<=n;i++){
cin>>a[i];
}
int now=0;
priority_queue<pii> q;
for(int i=1;i<=n;i++){
now++;
while(q.size()){
auto [val,cnt]=q.top();
if(val>a[i]){
now+=val*cnt;
q.pop();
q.push({a[i],now/a[i]});
now%=a[i];
}
else break;
}
if(now>=a[i]){
q.push({a[i],now/a[i]});
now%=a[i];
}
}
int ans=0;
while(q.size()){
ans+=q.top().second;
q.pop();
}
cout<<ans;
}
signed main(){
ios::sync_with_stdio(0);
cin.tie(0);
int ct=1;
// cin>>ct;
while(ct--){
solve();
}
return 0;
}
Problem M. 有效算法
一眼二分,显然当k在大于一个值后就一定满足条件了。
对每一个a[i],可以找到x的一个区间,如果所有区间有并集,则返回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;
//using i128 = __int128_t;
const ll inf = 1e18;
const int mod = 998244353;
void solve(){
int n;
cin>>n;
vector<int> a(n+10),b(n+10);
for(int i=1;i<=n;i++){
cin>>a[i];
}
for(int i=1;i<=n;i++){
cin>>b[i];
}
auto check=[&](int k)->bool {
int l=-inf,r=inf;
for(int i=1;i<=n;i++){
int tl=a[i]-k*b[i], tr=a[i]+k*b[i];
if(i==1){
l=tl;
r=tr;
}
else{
if(tl<l){
if(tr<l) return 0;
else r=min(r,tr);
}else{
//tl >= l
if(tl>r) return 0;
else{
l=tl;
r=min(tr,r);
}
}
}
// if(k==1) cout<<a[i]<<" "<<l<<" "<<r<<endl;
}
return 1;
};
int l=0,r=1e9;
while(l<r){
int mid=l+r>>1;
if(check(mid)) r=mid;
else l=mid+1;
}
cout<<l<<endl;
}
signed main(){
ios::sync_with_stdio(0);
cin.tie(0);
int ct=1;
cin>>ct;
while(ct--){
solve();
}
return 0;
}
J. 排列与合数
纯纯诈骗题,当有偶数的时候,直接把偶数诺到第一位即可。
否则直接输出样例里给出的那个就行。
注意特判前导零
点击查看代码
#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;
//using i128 = __int128_t;
const ll inf = 1e18;
const int mod = 998244353;
void solve(){
string s;
cin>>s;
for(int i=0;i<5;i++){
if(s[i]=='0'){
swap(s[i],s[4]);
cout<<s<<endl;
return;
}
}
for(int i=0;i<5;i++){
int val=s[i]-'0';
if(val%2==0){
swap(s[i],s[4]);
cout<<s<<endl;
return;
}
}
cout<<"97531"<<endl;
}
signed main(){
ios::sync_with_stdio(0);
cin.tie(0);
int ct=1;
cin>>ct;
while(ct--){
solve();
}
return 0;
}
L. Toxel 与 PCPC II
一开始写了个贪心,wa了一发发现好像不太能贪
dp,因为四次方的数值增长很大,所以dp[i]一定不会从离的很远地方转移过来,可以设成70
点击查看代码
#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;
//using i128 = __int128;
const ll inf = 1e18;
const int mod = 998244353;
void solve(){
int n,m;
cin>>n>>m;
vector<int> a(m+10),f(m+10,inf);
for(int i=1;i<=m;i++){
cin>>a[i];
}
f[0]=0;
for(int i=1;i<=m;i++){
int l=max(0ll,i-70);
for(int j=l;j<i;j++){
int val=f[j]+pow(i-j,4)+a[i];
f[i]=min(f[i],val);
}
}
cout<<f[m]<<endl;
}
signed main(){
ios::sync_with_stdio(0);
cin.tie(0);
int ct=1;
// cin>>ct;
while(ct--){
solve();
}
return 0;
}
H. 随机栈
每次取数时,只能取集合里的最小值。
所以每次取数时,给答案成上取出最小值的概率即可。
在放数时,如果放的数比之前已经取出的数要小,则直接输出0.
点击查看代码
#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;
//using i128 = __int128_t;
const ll inf = 1e18;
const int mod = 998244353;
int qmi(int a,int b,int p){
int res=1;
while(b){
if(b&1) res=res*a%p;
a=a*a%p;
b>>=1;
}
return res;
}
void solve(){
int n;
cin>>n;
int ans=1;
int sum=0;
int now=-inf;
map<int,int> mp;
priority_queue<int,vector<int>,greater<int>> q;
for(int i=1;i<=2*n;i++){
int ch;
cin>>ch;
if(ch==-1){
int val=q.top();
q.pop();
now=val;
//ans*=mp[val]/sum
// cout<<mp[val]<<" "<<sum<<endl;
ans*=mp[val]*qmi(sum,mod-2,mod)%mod;
ans%=mod;
mp[val]--;
sum--;
}
else{
if(ch<now){
cout<<0<<endl;
return;
}
sum++;
q.push(ch);
mp[ch]++;
}
}
cout<<ans<<endl;
}
signed main(){
ios::sync_with_stdio(0);
cin.tie(0);
int ct=1;
// cin>>ct;
while(ct--){
solve();
}
return 0;
}
K. 树上问题
换根dp很板的题,每次把根从u换到子节点v时,只有u和v的值会发生变化,在dfs时维护u和v的值即可
点击查看代码
#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;
//using i128 = __int128_t;
const ll inf = 1e18;
const int mod = 998244353;
void solve(){
int n;
cin>>n;
vector<int> w(n+10);
vector<vector<int>> g(n+10);
for(int i=1;i<=n;i++){
cin>>w[i];
}
for(int i=1;i<n;i++){
int u,v;
cin>>u>>v;
g[u].push_back(v);
g[v].push_back(u);
}
int ans=0;
int sum=0;
vector<int> f(n+10);//每个节点是否满足条件
auto dfs1=[&](auto dfs1,int u,int pre)->void {
if(w[u]>=(w[pre]+1)/2){
f[u]=1;
sum++;
}
for(auto v:g[u]){
if(v==pre) continue;
dfs1(dfs1,v,u);
}
};
dfs1(dfs1,1,0);
if(sum==n) ans++;
// cout<<f[5]<<" "<<sum<<endl;
auto dfs=[&](auto dfs,int u,int pre)->void {
for(auto v:g[u]){
if(v==pre) continue;
int tv=f[v],tu=f[u];
if(u==1 && v==5){
// cout<<f[5]<<" "<<sum<<endl;
}
if(f[v]==0){
f[v]=1;
sum++;
}
if(w[u]>=(w[v]+1)/2){
if(f[u]==0){
f[u]++;
sum++;
}
}
else{
if(f[u]==1){
f[u]--;
sum--;
}
}
if(sum==n) ans++;
dfs(dfs,v,u);
sum+=tv-f[v];
sum+=tu-f[u];
f[v]=tv;
f[u]=tu;
if(u==1 && v==5){
// cout<<f[v]<<" "<<sum<<endl;
}
}
};
dfs(dfs,1,0);
cout<<ans<<endl;
}
signed main(){
ios::sync_with_stdio(0);
cin.tie(0);
int ct=1;
cin>>ct;
while(ct--){
solve();
}
return 0;
}
Problem A. Once In My Life
vp时看了会没啥想法的题,24的学弟学妹说这题不是简单题吗?
假设构造的数是 t = 123456789d0000,最后0的个数是n的位数
假设t不是n的倍数,要给t加上一些数,满足t是n的倍数
具体的,要给 t += (n-t%n)
最后t/n就是答案
点击查看代码
#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;
//using i128 = __int128_t;
const ll inf = 1e18;
const int mod = 998244353;
void solve(){
int n,d;
cin>>n>>d;
int tmp=n;
int t=1234567890+d;
int cnt=0;
while(tmp){
tmp/=10;
cnt++;
}
t*=pow(10,cnt);
t+=(n-t%n);
cout<<t/n<<endl;
// cout<<t%n<<endl;
}
signed main(){
ios::sync_with_stdio(0);
cin.tie(0);
int ct=1;
cin>>ct;
while(ct--){
solve();
}
return 0;
}
Problem C. 中二病也要打比赛
首先假设有两个相同的数a[i] = a[j],则 i ~ j 这一段最后都要推平,就是让这一段的值都相等,就是在这段区间中,选一个数x,f(x)=x,其他的值f(y)=x (y!=x)。所以这一段区间中,一共有sum种数,则代价一定是sum-1
如果两端区间相交怎么办?
例如 1,2,3,2,3,5
则一定要让a[2]到a[5]都一样。
换句话说,如果有两个区间要推平,且两个区间相交,则这两个区间必须要推平一个数
考虑全局的最长非严格上升子序列,则每个区间内选且仅选一种数字。
为了让每段区间之选一种数字,可以给每段要推平的区间,从大到小排序,再对整个数组计算lis
处理的时候有三个技巧
-
如何找到每段要从大到小排序的段?因为有出现区间会相交的可能,我们可以找到每个数字,第一次出现的地方fir,和最后一次出现的地方lst,对这段区间差分,给d[fir]+1, d[lst]-1。最后前缀和后,等于0的地方就是一段区间的终点
-
因为要统计每段种有多少种数,的f(x)!=x, 可以计算整个数组的严格lis(严格lis中不会出现相同的数),此时在每个段中,只会选一个数,统计整个数组有多少个数,减去严格lis的长度,即为答案
-
因为n是1e5,所以要二分的找lis。
点击查看代码
#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;
//using i128 = __int128_t;
const ll inf = 1e18;
const int mod = 998244353;
void solve(){
int n;
cin>>n;
int cnt=0;
vector<int> a(n+10),fir(n+10),lst(n+10),d(n+10);
for(int i=1;i<=n;i++){
cin>>a[i];
if(fir[a[i]]==0){
fir[a[i]]=i;
cnt++;
}
lst[a[i]]=i;
}
for(int i=1;i<=n;i++){
d[fir[i]]++;
d[lst[i]]--;
}
for(int i=1;i<=n;i++){
d[i]+=d[i-1];
}
int pre=1;
for(int i=1;i<=n;i++){
if(d[i]==0){
sort(a.begin()+pre,a.begin()+i+1,greater<int>());
pre=i+1;
}
}
int len=1;
d.assign(n+10,0);
d[1]=a[1];
for(int i=2;i<=n;i++){
if(a[i]>d[len]){
d[++len]=a[i];
}
else{
int l=1,r=len;
while(l<r){
int mid=l+r>>1;
if(d[mid]>=a[i]) r=mid;
else l=mid+1;
}
d[l]=a[i];
}
}
cout<<cnt-len<<endl;
}
signed main(){
ios::sync_with_stdio(0);
cin.tie(0);
int ct=1;
// cin>>ct;
while(ct--){
solve();
}
return 0;
}
Problem D. 距离之比
显然答案最小是1,最大是在两点的连线是45°或135°时,取到最大值根号2
且这个取值只跟两点连线的角度有关系。
可以把坐标轴逆时针旋转45°,点坐标转换成 ( x+y ,x-y )
此时的y轴也就是135°,x轴就是45°
先对所有点,按照x+y排序,此时两个相邻点a,a+1在x-y上的差距一定小于不相邻的点a,a+2
x-y同理
所以两次排序分别对相邻点计算答案取最大值即可
点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
using pii=pair<int,int>;
using pdd=pair<double,double>;
using ll = long long;
using ull = unsigned long long;
//using i128 = __int128_t;
const ll inf = 1e18;
const int mod = 998244353;
void solve(){
int n;
cin>>n;
vector<pdd> a(n+1);
for(int i=1;i<=n;i++){
cin>>a[i].first>>a[i].second;
// scanf("%lf %lf",&a[i].first,&a[i].second);
}
sort(a.begin()+1,a.end(),[&](pdd x,pdd y)-> bool {
return x.first+x.second<y.first+y.second;
});
double ans=1.0;
auto getdist = [&](pdd t1,pdd t2)->double {
auto [x1,y1]=t1;
auto [x2,y2]=t2;
double dx=abs(x1-x2);
double dy=abs(y1-y2);
return (dx+dy)/sqrt((dx*dx+dy*dy));
};
for(int i=1;i<n;i++){
auto [x1,y1]=a[i];
auto [x2,y2]=a[i+1];
double val=getdist(a[i],a[i+1]);
ans=max(ans,val);
}
sort(a.begin()+1,a.end(),[&](pdd x,pdd y)-> bool {
return x.first-x.second<y.first-y.second;
});
for(int i=1;i<n;i++){
auto [x1,y1]=a[i];
auto [x2,y2]=a[i+1];
double val=getdist(a[i],a[i+1]);
ans=max(ans,val);
}
printf("%.11f\n",ans);
}
signed main(){
// ios::sync_with_stdio(0);
// cin.tie(0);
int ct=1;
cin>>ct;
while(ct--){
solve();
}
return 0;
}

浙公网安备 33010602011771号