A. 数论
数学题,经实践证明,这个题可以$AC$。
考试时打的暴力,拿到$20$分。
正解:
虽然现在思路还是有点模糊,但是大体的思路应该差不多。
首先,就像题解说的,如果对于一个非良好数$x$,$xp^c$也是非良好数,其中$p$为质数,$c>=0$。
前提是$x$中不含质因子$p$。
$xp^c$的约数个数$val[xp^c]$是$val[x]\times (c-1)$。
$x$中没有质因子$p$,当然他的约数中也没有质因子$p$,所以每次乘$p$都会形成新的约数。
之后就是一些玄学的操作。
每次"迭代"$($新名词$)$,把原序列中的数拓展,
先枚举素数,再枚举原序列中的数,再枚举指数,把新数存到临时的数组中。
最后删去新的临时数组中不合法的数,因为这个数不会再更新别的数了。
注意要排序。
再维护一个堆,维护前$k+1$大的约数个数。
这样就可以删掉当前不合法的数了,应该是当前,因为之后插进来的数还可能再使它不合法。
最后一定可以筛掉所有不合法的数。
最后$TLE70$。
丑陋的代码:

#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdio>
#include<queue>
#define _min(x,y) ((x)<(y)?(x):(y))
#define _max(x,y) ((x)>(y)?(x):(y))
#define _abs(x) ((x)<0?(-1*(x)):(x))
#define Maxn 1000050
#define Reg register
//#define int long long
#define int __int128
using namespace std;
bool vis[Maxn];
int T,K,m,tot,top[2],pri[Maxn],ans[Maxn];
struct Node {int x,cnt;} stack[2][Maxn],lss[Maxn];
bool comp(Node a,Node b) {return a.x<b.x;}
priority_queue<Node,vector<int>,greater<int> > q;
void print(int x)
{
if(x>=10) print(x/10);
putchar(x%10+48);
return;
}
signed main()
{
scanf("%lld%lld%lld",&T,&K,&m);
for(Reg int i=2;i<=10000;++i)
{
if(vis[i]) continue;
pri[++tot]=i;
if(tot==300) break;
for(Reg int j=2;j*i<=100000;++j) vis[j*i]=1;
}
stack[0][++top[0]]=(Node){1,1};
for(Reg int i=1;i<=tot;++i)
{
int cur=i&1,pre=(i+1)&1;
top[cur]=0;
for(Reg int j=1;j<=top[pre];++j)
{
int lss=1,x=stack[pre][j].x,p=stack[pre][j].cnt;
for(Reg int k=0;k<=60;++k)
{
if(lss*x>m||lss*x<0) break;
stack[cur][++top[cur]]=(Node){lss*x,p*(k+1)};
lss*=pri[i];
}
}
sort(stack[cur]+1,stack[cur]+top[cur]+1,comp);
int tok=0;
while(!q.empty()) q.pop();
for(Reg int j=1;j<=top[cur];++j)
{
if(stack[cur][j].x==1)
{
if(q.size()>=K+1&&stack[cur][j].cnt-1<q.top()) continue;
lss[++tok]=stack[cur][j];
q.push(stack[cur][j].cnt-1);
while(q.size()>K+1) q.pop();
}
else
{
if(q.size()>=K+1&&stack[cur][j].cnt-2<q.top()) continue;
lss[++tok]=stack[cur][j];
q.push(stack[cur][j].cnt-2);
while(q.size()>K+1) q.pop();
}
}
top[cur]=0;
for(Reg int j=1;j<=tok;++j) stack[cur][++top[cur]]=lss[j];
}
// for(Reg int i=1;i<=top[tot&1];++i) cout<<stack[tot&1][i].x<<endl;
while(T--)
{
int x; scanf("%lld",&x);
print(stack[tot&1][x].x); puts("");
}
return 0;
}
B. 位运算
正解:
用$dp[i][j]$表示第$i$个数,到这时一共有$j$个$1$,这种情况是否存在$(0/1)$。
记录一下前趋,因为只要一组合法解,所以出现一个记录一个就好了。
转移的话分三种情况:
符号为$\&$,这时下界是$max(0,j+a[i+1]-m)$,上界是$min(j,a[i+1])$,
符号为$|$,这时下界是$max(j,a[i+1])$,上界是$min(m,j+a[i+1])$,
符号为^,这时下界是$|j-a[i+1]|$,上界是$2m-j-a[i+1]$。
就是这样,然后记录前趋。
最后用$dfs$还原原数,很麻烦,很多细节。
丑陋的代码:

#include<iostream>
#include<cstring>
#include<cstdio>
#define _min(x,y) ((x)<(y)?(x):(y))
#define _max(x,y) ((x)>(y)?(x):(y))
#define _abs(x) ((x)<0?(-1*(x)):(x))
#define Maxn 100050
#define Reg register
int n,m,c,opt[Maxn],dp[Maxn][35],pre[Maxn][35],A[Maxn],num[Maxn];
char s[10];
int get(int x)
{
int s=0;
while(x) {if(x&1) ++s; x>>=1;}
return s;
}
void dfs(int now,int st)
{
if(now==1) {num[1]=st; return;}
if(opt[now-1]==1)
{
int nxt=pre[now][get(st)],nst=st,npt=st;
for(Reg int i=1;i<=m;++i)
{
if(get(nst)==nxt) break;
if(!(nst&(1<<(i-1)))) nst|=(1<<(i-1));
}
for(Reg int i=1;i<=m;++i)
{
if(get(npt)==A[now]) break;
if(((st&(1<<(i-1)))&&!(nst&(1<<(i-1))))) npt|=(1<<(i-1));
else if((!(st&(1<<(i-1)))&&!(nst&(1<<(i-1))))) npt|=(1<<(i-1));
}
num[now]=npt; dfs(now-1,nst);
}
else if(opt[now-1]==2)
{
int nxt=pre[now][get(st)],nst=st;
for(Reg int i=1;i<=m;++i)
{
if(get(nst)==nxt) break;
if(nst&(1<<(i-1))) nst^=(1<<(i-1));
}
int npt=nst^st;
for(Reg int i=1;i<=m;++i)
{
if(get(npt)==A[now]) break;
if(nst&(1<<(i-1))) npt|=(1<<(i-1));
}
num[now]=npt; dfs(now-1,nst);
}
else
{
int nxt=pre[now][get(st)],nst=0,npt=0;
for(Reg int i=1;i<=m;++i)
{
if(!(st&(1<<(i-1)))) continue;
if(get(nst)<nxt) nst|=(1<<(i-1));
else npt|=(1<<(i-1));
}
for(Reg int i=1;i<=m;++i)
{
if(nxt-get(nst)==A[now]-get(npt)) break;
if(nxt-get(nst)>A[now]-get(npt)&&(npt&(1<<(i-1)))&&!(nst&(1<<(i-1)))) nst^=(1<<(i-1)),npt^=(1<<(i-1));
if(nxt-get(nst)<A[now]-get(npt)&&(nst&(1<<(i-1)))&&!(npt&(1<<(i-1)))) nst^=(1<<(i-1)),npt^=(1<<(i-1));
}
for(Reg int i=1;i<=m;++i)
if(!(st&(1<<(i-1)))&&get(nst)<nxt&&get(npt)<A[now]) nst|=(1<<(i-1)),npt|=(1<<(i-1));
num[now]=npt; dfs(now-1,nst);
}
return;
}
int main()
{
scanf("%d%d%d",&n,&m,&c);
for(Reg int i=1;i<=n-1;++i)
{
scanf("%s",s);
if(s[0]=='A') opt[i]=1;
else if(s[0]=='O') opt[i]=2;
else opt[i]=3;
}
for(Reg int i=1;i<=n;++i) scanf("%d",&A[i]);
dp[1][A[1]]=1;
for(Reg int i=1;i<=n-1;++i)
{
for(Reg int j=0;j<=m;++j)
{
if(!dp[i][j]) continue;
if(opt[i]==1) for(Reg int k=_max(0,j-(m-A[i+1]));k<=_min(j,A[i+1]);++k) dp[i+1][k]=1,pre[i+1][k]=j;
else if(opt[i]==2) for(Reg int k=_max(j,A[i+1]);k<=_min(m,j+A[i+1]);++k) dp[i+1][k]=1,pre[i+1][k]=j;
else
{
int mxx=(m-A[i+1]>j)?(A[i+1]+j):(2*m-j-A[i+1]);
for(Reg int k=_abs(j-A[i+1]);k<=mxx;k+=2) dp[i+1][k]=1,pre[i+1][k]=j;
}
}
}
if(!dp[n][get(c)]) printf("OvO");
else
{
dfs(n,c);
for(Reg int i=1;i<=n;++i) printf("%d ",num[i]);
}
return 0;
}
C. 旅行
总结:
这次考试题目挺好的,做题改题都挺费劲的。
其实这次考试也没必要谈心态,因为心态好了也想不出来。。。
说考试过程吧,
前$40$分钟看$T1$,开始打$k=0$的表,企图能找到一些规律。
发现前几项非常像等差数列。。其实没什么规律。
而且暴力分给的非常少。
之后看$T2$,发现居然有暴力分。。
打完搜索,然后看特殊性质$1$,应该能拿到一些分。
然后码了一个小时左右,嗯,以为$T2$的暴力分能拿满。
最后$T3$,心想可能教练故意难度递减排序,应该可做,然后想了一会儿,还是不可做。。
滚回去看$T1$,依然没什么思路,快速码了一个暴力,水到$20$分。
最后$20+10+0=30$。
没什么水平。。。
