2022NOIP A层联测13 QAQ 游戏 QWQ 修炼
[记忆化搜索+hash映射卡常]给出一个x0,你可以进行n次操作,每次有p的概率把x0变成x0>>1,否则变成x0+1.求最终的值的2次幂指数的期望。(x0<=1e9,n<=2000)
考场
暴力
正解
考虑dfs过程中的参数\(dfs(now,val_x)\),now有2000次,而且\(val_x\)的值也不超过2000个,因为>>后x的集设为X,发现+1的操作最多每次产生2个数,+1刚好又有n个,感性理解x0集合不会太多,所以记忆化。
\(O(n^2log(n))\),但是不能用unordered_map卡常因为pair没有hash值,所以手写hash表。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define rint register int
#define chu printf
#define ll long long
#define ull unsigned ll
inline ll re()
{
ll x=0,h=1;char ch=getchar();
while(ch<'0'||ch>'9')
{
if(ch=='-')h=-1;
ch=getchar();
}
while(ch>='0'&&ch<='9')
{
x=(x<<1)+(x<<3)+(ch^48);
ch=getchar();
}
return x*h;
}
const int mod=998244353;
const ll moo=20060611;
const ull base=88765423;
int n,x0,p;
//数组怎么记忆?
struct Hashtable
{
ull key;
ll val;
int nxt;
}e[moo+10];
int head[moo+10],tot;
inline void insert(ull v,ll val)
{
int u=v%moo;
for(rint i=head[u];i;i=e[i].nxt)
{
if(e[i].key==v)return;
}
e[++tot]=(Hashtable){v,val,head[u]
};
head[u]=tot;
}
inline int query(ull P)
{
int u=P%moo;
for(rint i=head[u];i;i=e[i].nxt)
{
if(e[i].key==P)return e[i].val;
}
return -1;
}
inline int Count(int x)
{
int op=0;
while(x>1&&x%2==0)x/=2,++op;
return op;
}
inline int dfs(int lef,int now)
{
if(lef==0)return Count(now);
ull kp=1llu*lef*base+1llu*now;
int fid=query(kp);
if(fid==-1)
{
//需要自己更新状态
int nowans=0;
nowans=(ll)p*dfs(lef-1,now>>1)%mod;//如果选择它
nowans=(nowans+(ll)(1-p+mod)*dfs(lef-1,now+1)%mod)%mod;
insert(kp,nowans);
return nowans;
}
else
{
return fid;
}
}
int main()
{
// freopen("restart3.in","r",stdin);
// freopen("1.txt","w",stdout);
freopen("qaq.in","r",stdin);
freopen("qaq.out","w",stdout);
x0=re(),n=re(),p=re();
//还剩lef步,现在值是x的期望
chu("%d",dfs(n,x0));
return 0;
}
/*
5 3 3
514 114 1919810
*/
[数学:哥德巴赫猜想]给出n,求一种对n的整数划分方案使得所有数集的因数个数最少/最多。求集合中的数,给出方案。(n<=1e9)
暴力
\(map<int,int>dp[i]:表示数字i的划分方案,用int st[i]记录因数个数\),循环1--n,枚举最后一个划分出来的数字集合,
\(st[i]=max(st[i],st[i-k]+st[k])\),最后记录路径更新map。
\(O(n^2)\),打表发现多数选择是质数,可以只用质数转移。
提前线性筛预处理:num[x]:x中含有的因数个数;ci[x]:x最小质因数的指数(快速求出因数个数)
点击查看代码
// ubsan: undefined
// accoders
#include <bits/stdc++.h>
#include "game.h"
#define chu printf
#define rint register int
#define ll long long
using namespace std;
bool fl;
int prime[300000 + 100], cnt, ci[300000 + 100], num[300000 + 100], cp[300000 + 100], f[300000 + 100];
map<int, int> Mi[100000 + 100];
bool frog[300000 + 100];
bool isprime(int x) {
for (ll i = 2; i * i <= x; ++i) {
if (x % i == 0)
return 0;
}
return 1;
}
int Count(int x) {
if (x == 1)
return 1;
int o = 0;
for (ll i = 1; i * i <= x; ++i) {
if (x % i == 0) {
++o;
if (x / i != i)
++o;
}
}
return o;
}
void pre() {
ci[1] = 1;
num[1] = 1; //边界!
for (rint i = 2; i <= 300000; ++i) {
if (!frog[i]) {
prime[++cnt] = i;
ci[i] = 1;
num[i] = 2;
}
cp[i] = cnt;
int k;
for (rint j = 1; j <= cnt && (k = prime[j] * i) <= 300000; ++j) {
frog[k] = 1;
if (i % prime[j] == 0) {
ci[k] = ci[i] + 1;
num[k] = num[i] / (ci[i] + 1) * (ci[k] + 1);
break;
}
ci[k] = 1;
num[k] = num[i] * 2;
}
}
f[1] = 1;
Mi[1][1] = 1;
for (rint i = 2; i <= 100000; ++i) {
int now = i;
f[i] = num[i]; //初始值!
for (rint j = cp[i]; prime[j] >= i / 2; --j) {
if (f[i] > f[i - prime[j]] + f[prime[j]]) {
f[i] = f[i - prime[j]] + f[prime[j]];
now = prime[j];
}
}
if (now != i) {
for (auto v : Mi[now]) Mi[i][v.first] += v.second;
now = i - now;
for (auto v : Mi[now]) Mi[i][v.first] += v.second;
} else
Mi[now][now] = 1;
}
}
pair<map<int, int>, map<int, int> > solve(int n) {
if (!fl) {
pre();
fl = 1;
}
map<int, int> mi, mx;
mi.clear();
mx.clear();
mx[1] = n;
int ans = 1e9;
if (n <= 100000) {
mi = Mi[n];
} else {
if (n & 1) //如果是奇数,不过质数怎么判断?
{
if (isprime(n)) // 2
{
mi[n] = 1;
ans = 2;
} else if (isprime(n - 2)) // 4
{
mi[1] = 2;
mi[n - 2] = 1;
ans = 4;
} else {
int ur = n - 1;
mi[1] = 1;
for (rint ir = 1; ir <= cnt; ++ir) // 5
{
int i = prime[ir];
int x = i, y = ur - i;
if (isprime(x) && isprime(y)) {
mi[x] = 1;
mi[y] = 1;
break;
}
}
ans = 5;
}
} else {
if (isprime(n - 1)) {
mi[n - 1] = 1;
mi[1] = 1;
ans = 3;
} else {
for (rint i = 1; i <= cnt; ++i) {
int ir = prime[i];
if (isprime(ir) && isprime(n - ir)) {
mi[ir] = 1;
mi[n - ir] = 1;
}
}
ans = 4;
}
}
int u = Count(n);
if (u < ans) {
mi.clear();
mi[n] = 1;
}
}
return make_pair(mi, mx);
}
正解
一个>2的偶数可以被划分成2个质数的加和
考虑对于偶数
【1】划分成1+质数-->3
【2】质数+质数-->4
【3】2-->2
对于奇数
【1】1+偶数的所有方案
所有数
自己的因子个数
上界是5
关于凑质数
可以证明一定存在一种方案使得所选择的质数<=3e5,不会证......
点击查看代码
#include<bits/stdc++.h>
#include"game.h"
#define chu printf
#define rint register int
#define ll long long
using namespace std;
bool fl;
int prime[300000+100],cnt,ci[300000+100],num[300000+100],cp[300000+100],f[300000+100];
map<int,int>Mi[100000+100];
bool frog[300000+100];
bool isprime(int x)
{
if(x<=0)return false;
for(ll i=2;i*i<=x;++i)
{
if(x%i==0)return 0;
}
return 1;
}
int Count(int x)
{
if(x==1)return 1;int o=0;
for(ll i=1;i*i<=x;++i)
{
if(x%i==0)
{
++o;
if(x/i!=i)++o;
}
}
return o;
}
void pre()
{
ci[1]=1;num[1]=1;//边界!
for(rint i=2;i<=300000;++i)
{
if(!frog[i])
{
prime[++cnt]=i;
ci[i]=1;num[i]=2;
}
cp[i]=cnt;
int k;
for(rint j=1;j<=cnt&&(k=prime[j]*i)<=300000;++j)
{
frog[k]=1;
if(i%prime[j]==0)
{
ci[k]=ci[i]+1;
num[k]=num[i]/(ci[i]+1)*(ci[k]+1);
break;
}
ci[k]=1;
num[k]=num[i]*2;
}
}
f[1]=1;
Mi[1][1]=1;
for(rint i=2;i<=100000;++i)
{
int now=i;
f[i]=num[i];//初始值!
for(rint j=cp[i];prime[j]>=i/2;--j)
{
if(f[i]>f[i-prime[j]]+f[prime[j]])
{
f[i]=f[i-prime[j]]+f[prime[j]];
now=prime[j];
}
}
if(now!=i)
{
for(auto v:Mi[now])Mi[i][v.first]+=v.second;
now=i-now;
for(auto v:Mi[now])Mi[i][v.first]+=v.second;
}
else Mi[now][now]=1;
}
}
pair<map<int,int>,map<int,int> > solve(int n)
{
if(!fl)
{
pre();fl=1;
}
map<int,int>mi,mx;
mi.clear();mx.clear();
mx[1]=n;
int ans=1e9;
if(n<=100000)
{
mi=Mi[n];
}
else
{
if(n&1)//如果是奇数,不过质数怎么判断?
{
if(isprime(n))//2
{
mi[n]=1;
ans=2;
}
else if(isprime(n-2))//4
{
mi[1]=2;
mi[n-2]=1;
ans=4;
}
else
{
int ur=n-1;
mi[1]=1;
for(rint ir=1;ir<=cnt;++ir)//5
{
int i=prime[ir];
int x=i,y=ur-i;
if(isprime(x)&&isprime(y))
{
mi[x]=1;mi[y]=1;break;
}
}
ans=5;
}
}
else
{
if(isprime(n-1))
{
mi[n-1]=1;
mi[1]=1;
ans=3;
}
else
{
for(rint i=1;i<=cnt;++i)
{
int ir=prime[i];
if(isprime(ir)&&isprime(n-ir))
{
// chu("%d and %d\n",ir,n-ir);
mi[ir]=1;
mi[n-ir]=1;
ans=4;
break;
}
}
}
}
int u=Count(n);
// chu("ans:%d u;%d\n",u,ans);
if(u<ans)
{
mi.clear();
mi[n]=1;
// chu("with");
}
}
// for(auto e:mi)
// {
// chu("%d %d\n",e.first,e.second);
// }
return make_pair(mi,mx);
}
[修炼]给出序列A,求配对方案,如果ai和ai+1配对,代价是\(ai+a_{i+1}-x\),否则是自己的ai,求配对个数是[1--n/2]的最小代价。(n<=2e5)
考场
因为只能相邻的数进行选择,所以\(dp[i][0/1][j]:表示目前选择到i,i和前面合并/不合并,选了j个配对的最小代价\)
,转移就行。\(O(n^2)\)
正解
可反悔贪心
点击查看代码
// ubsan: undefined
// accoders
#include<bits/stdc++.h>
//#include<game.h>!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
using namespace std;
#define rint register int
#define chu printf
#define ll long long
inline ll re()
{
ll x=0,h=1;char ch=getchar();
while(ch<'0'||ch>'9')
{
if(ch=='-')h=-1;
ch=getchar();
}
while(ch>='0'&&ch<='9')
{
x=(x<<1)+(x<<3)+(ch^48);
ch=getchar();
}
return x*h;
}
const int N=2e5+10;
int n,x,a[N],pre[N],nxt[N];
ll ans;
priority_queue<pair<ll,int> >q;
ll val[N];
bool vs[N];
inline void del(int id)
{
nxt[pre[pre[id]]]=id;
pre[nxt[nxt[id]]]=id;//x和x+1配对了,所以默认x-1和x+1的向后配对都不合法
//修改val值,就是id位置反悔的价值
val[id]=val[pre[id]]+val[nxt[id]]-val[id];
vs[pre[id]]=vs[nxt[id]]=1;
pre[id]=pre[pre[id]];
nxt[id]=nxt[nxt[id]];
}
int main()
{
// freopen("restart3.in","r",stdin);
// freopen("1.txt","w",stdout);
freopen("restart.in","r",stdin);
freopen("restart.out","w",stdout);
n=re(),x=re();
for(rint i=1;i<=n;++i)
{
a[i]=re();
ans+=a[i];
}
for(rint i=1;i<n;++i)
{
val[i]=min(x,a[i]+a[i+1]);
pre[i]=i-1;
nxt[i]=i+1;
q.push(make_pair(val[i],i));
}
nxt[n]=0;pre[n]=n-1;
nxt[0]=1;
val[0]=-1e15,val[n]=-1e15;//不合法位置赋值
for(rint i=1;i<=n/2;++i)
{
while(!q.empty()&&vs[q.top().second])
{
q.pop();
}
ll y=q.top().second;q.pop();
ans-=val[y];
del(y);
q.push(make_pair(val[y],y));
chu("%lld\n",ans);
}
return 0;
}
/*
6 10
5 6 3 5 4 5
*/