基科集训总结(9.23-10.5)
集训总结
10a8beef-ac3e-4a9a-9bb4-9e6f23eb0d82
Content
0923比赛小结
A 防签到题(NKOJ P9158)
题目描述
系统中有一个神奇函数:
unsigned func(unsigned n) {
unsigned x = n & -n;
return (n + x) | ((n ^ (n + x)) / x) >> 2;
}
但由于C++数据类型的限制,这个函数只适用于32位无符号整数(即unsigned类型)的计算。
你的任务是,实现同样功能但支持k位无符号整数的函数。
数据范围:$2<=k<=64,$ $0<n<2^k$
考点:二进制/模拟
思路
这道题就一个字:坑 ,答案要$mod$ $2^k$,不是将函数直接复制下来就行了,还要注意一下$k=64$的时候要特判一下。函数实现直接用unsigned long long存一下就行了,模拟即可。
Code
#include <cstdio>
#include <algorithm>
#include <cstring>
#define ull unsigned long long
using namespace std;
unsigned func(unsigned n) {
unsigned x = n & -n;
return (n + x) | ((n ^ (n + x)) / x) >> 2;
}
ull f(ull n, int k)
{
n <<= (64 - k);
n >>= (64 - k);
return n;
}
int main()
{
// freopen("anti.in", "r", stdin);
// freopen("anti.out", "w", stdout);
int T;
scanf("%d", &T);
while (T--)
{
ull n;
int k;
scanf("%llu %d", &n, &k);
ull x;
x = n & -n;
ull y = f(x + n, k);
ull z = f(n ^ y, k);
z = z / x;
z >>= 2;
ull ans = f(y | z, k);
printf("%llu\n", ans);
}
return 0;
}
其实题目给出的代码就是一个数 $n$的二进制数中的1个数相等的比它大的第一个二进制排列的数是多少。
心得:当时看到这道题感觉很懵,随便打了一个代码最后只得了30分,不知道怎么做和考的什么,以前也没见过这样类似的题,赛后感觉又很简单,证明还是需要多见多识广。
B 勇往直前(NKOJ P10646)
做法:显然DP
设$f_i$表示经过$i$时而且$i$激活时传送到原来的位置时到$i$的所需的步数。 则转移式为:$f_i=\sum\limits_{j=1}^{i-1}f_j(a_j>b_i)$
意思是在返回的时候把区间内的所有被激活状态又重新加一次。
时间复杂度:$O(n^2)$ 可以看出$f_i$转移的时候可以用前缀和和二分维护,即为$f_i=\sum\limits_{i=1}^{n}f_i(a_i=1)$
Code
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int mod=1e9+7;
const int N=2e5+50;
int f[N],p[N],n,m,ans;
struct node{
int a,b,t;
}x[N];
int s[N];
signed main(){
scanf("%lld%lld",&n,&m);
for(int i=1;i<=n;i++)scanf("%lld%lld%lld",&x[i].a,&x[i].b,&x[i].t),p[i]=x[i].a;
for(int i=1;i<=n;i++){
f[i]=(x[i].a%mod-x[i].b%mod)%mod;
int l=upper_bound(p+1,p+1+i,x[i].b)-p;
f[i]=((f[i]%mod+s[i-1]%mod)%mod-s[l-1]%mod)%mod;
s[i]=(s[i-1]%mod+f[i]%mod)%mod;
s[i]%=mod;
f[i]%=mod;
}
ans=m%mod;
for(int i=1;i<=n;i++){
if(x[i].t==1){
ans=(ans%mod+f[i]%mod)%mod;
}
}
cout<<(ans+mod)%mod<<endl;
return 0;
}
心得
当时我这道题1分也没有得到,这么明显的$dp$我却没想到,问题还是在平时的刷题太少了,需要勤加练习。
C 排列(NKOJ P10703)
做法
先引入一个广为人知的定理(哥德巴赫定理):
任一大于2的整数都可写成两个质数之和。
分两种情况讨论:
$1.n$为偶数 通过枚举尝试$n$以内的质数$i$使得$n-i$也为质数,在依次乘$j$在$mod n$。
$2.n$为奇数$n-1$内用处理偶数的方式处理,剩下的一个数就直接暴力枚举。
但需要注意,$n<8$时需要的特判一下不然会错。
Code
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N=2e5+50;
int t,n,primes[N],cnt,p[N],used[N];
bool isprime[N];
void euler(){
memset(isprime,1,sizeof(isprime));
isprime[1]=1;
for(int i=2;i<=N;i++){
if(isprime[i]){
primes[++cnt]=i;
}
for(int j=1;j<=cnt&&primes[j]*i<=N;j++){
isprime[i*primes[j]]=0;
if(i%primes[j]==0)break;
}
}
}
bool is_prime(int x){
if(x<=2)return false;
for(int i=2;i*i<=x;i++){
if(x%i==0)return false;
}
return true;
}
signed main(){
scanf("%lld",&t);
euler();
while(t--){
scanf("%lld",&n);
if(n==2){
cout<<"YES"<<endl;
cout<<"1 2"<<endl;
continue;
}
if(n==3){
cout<<"YES"<<endl;
cout<<"1 2 3"<<endl;
continue;
}
if(n==4){
cout<<"YES"<<endl;
cout<<"1 2 3 4"<<endl;
continue;
}
if(n==5){
cout<<"YES"<<endl;
cout<<"5 2 1 4 3"<<endl;
continue;
}
if(n==6){
cout<<"YES"<<endl;
cout<<"6 5 2 1 4 3"<<endl;
continue;
}
if(n==7){
cout<<"YES"<<endl;
cout<<"7 6 5 2 1 4 3"<<endl;
continue;
}
int p1=0;
if(n%2==0){
for(int i=2;i<=n;i++)if(isprime[i]&&isprime[n-i]){p1=i;break;}
cout<<"YES"<<endl;
for(int i=1;i<=n;i++)p[i]=(i*p1)%n+1;
for(int i=1;i<=n;i++)printf("%lld ",p[i]);
printf("\n");
}else{
memset(used,0,sizeof(used));
n--;
for(int i=2;i<=n;i++)if(isprime[i]&&isprime[n-i]){p1=i;break;}
cout<<"YES"<<endl;
for(int i=1;i<=n;i++)p[i]=(i*p1)%n+1,used[p[i]]=1;
int cntt=0,tot=0;
for(int j=2;j<=n-1;j++){
if(is_prime(p[j-1]+n+1)||is_prime(abs(p[j-1]-n-1))){
if(is_prime(n+1+p[j])||is_prime(abs(p[j]-n-1))){
tot=j-1;
}
}
}
if(is_prime(n+1+p[1])||is_prime(n+1-p[1]))tot=0;
if(is_prime(n+1+p[n])||is_prime(n+1-p[n]))tot=n;
if(tot==0)printf("%lld ",n+1);
for(int i=1;i<=n;i++){
if(i==tot)printf("%lld %lld ",p[i],cntt);
else printf("%lld ",p[i]);
}
if(tot==n)printf("%lld ",n+1);
printf("\n");
}
}
return 0;
}
心得
遇到构造体,首先不要慌,先打SPJ,在找规律,多刷题,就一定可以把它做出来
D 排版
思路 分三种情况讨论,在图片的上面;在图片的左右;在图片的下面,用倍增来做
代码
#include <algorithm>
#include <iostream>
#include <bits/stdc++.h>
using namespace std;
const int N=3e5+5;
int n,m,W,pw,lw,rw,a[N],sum[N],f[N][25];
int g[N][25],maxlen;
int midit(int sta,int lim){
if(sta>n || !lim)return 0;
int l=sta,r=n,mid,ret=l-1;
while(l<=r){
mid=(l+r)>>1;
if(sum[mid]-sum[sta-1]+mid-sta<=lim)
ret=mid,l=mid+1;
else r=mid-1;
}
return ret-sta+1;
}
void prework(){
int x,y;
for(int i=1;i<=n;i++){
f[i][0]=midit(i,W);
x=midit(i,lw);y=midit(i+x,rw);
g[i][0]=x+y;
}
for(int j=1;j<=maxlen;j++){
for(int i=1;i<=n;i++){
if(i+f[i][j-1]<=n)
f[i][j]=f[i][j-1]+f[i+f[i][j-1]][j-1];
else f[i][j]=N;
if(i+g[i][j-1]<=n)
g[i][j]=g[i][j-1]+g[i+g[i][j-1]][j-1];
else g[i][j]=N;
if(i+f[i][j]-1>n)f[i][j]=N;
if(i+g[i][j]-1>n)g[i][j]=N;
}
}
}
int solve(int s,int d){
int res=n,x=1,ans=0,tot=s-1;
for(int i=maxlen;i>=0;i--){
if(tot>=(1<<i) && res>=f[x][i] && x<=n)
tot-=(1<<i),res-=f[x][i],x+=f[x][i],ans+=(1<<i);
}
int pre=ans+d;
if(!res)return pre;
tot=d;
for(int i=maxlen;i>=0;i--){
if(tot>=(1<<i) && res>=g[x][i] && x<=n)
tot-=(1<<i),res-=g[x][i],x+=g[x][i],ans+=(1<<i);
}
ans=max(pre,ans);
if(!res)return ans;
for(int i=maxlen;i>=0;i--){
if(res>=f[x][i] && x<=n)
res-=f[x][i],x+=f[x][i],ans+=(1<<i);
}
return ans;
}
void work()
{
scanf("%d%d%d%d",&n,&W,&lw,&pw);
maxlen=log(n)/log(2)+1;rw=W-pw-lw;
for(int i=1;i<=n;i++)
scanf("%d",&a[i]),sum[i]=sum[i-1]+a[i];
prework();
int Q,x,y;cin>>Q;
while(Q--){
scanf("%d%d",&x,&y);
printf("%d\n",solve(x,y));
}
}
int main()
{
work();
return 0;
}
心得
对于以前学过的算法需要及时复习,要不到时考的时候又不会,也要对题目有基本的直觉。
10.5比赛小结
A题 数三角形
做法:
考虑到直接求取不好求,所以我们考虑另外一种方式:正难则反
首先我们可以知道 总三角形的个数为 $C_{n}^{3}$,就是求不是纯三角形的个数
怎么求呢?考虑对于每个点,都有$n-1$条边,不是纯三角形的个数为蓝色的边的个数在乘红色的边的个数。
所以直接求解即可。
Code
#include<bits/stdc++.h>
#define int long long
using namespace std;
int n;
char s[5005][5005];
int cnt1[5005],cnt2[5005],ans1,ans2,ans;
signed main(){
scanf("%lld",&n);
int ans2=(n-1)*(n-2)*n/6;
for(int i=1;i<=n-1;i++){
scanf("%s",s[i]+1+i);
for(int j=i+1;j<=n;j++){
if(s[i][j]=='1')cnt1[i]++,cnt1[j]++;
else if(s[i][j]=='0')cnt2[i]++,cnt2[j]++;
}
ans1+=cnt1[i]*cnt2[i];
}
ans1+=cnt1[n]*cnt2[n];
printf("%lld\n",ans2-ans1/2);
return 0;
}
B题 翻转
题目大意
给定一个$n*m$的矩阵$A$,$A_i,_j$ $\in$ $ 0,1$,求把序列改成全0矩阵的最小步数。
做法
考虑贪心,每次选最后一个非0数进行翻转即可
C题 果果系统
模拟即可
D题 凸
不会