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
*/
posted on 2022-10-22 20:13  HZOI-曹蓉  阅读(3)  评论(0编辑  收藏  举报