NOIP 提高组模拟赛5
math
emmmmmm,这题我跑了个奇奇怪怪的背包,然后用奇奇怪怪的优化,然后奇奇怪怪的AC了
upd: 做法假了,随便拍点随机数据就能卡掉.... (2022.7.11),新加了几个数据,不能AC了。....
先说我奇奇怪怪的想法,发现本题式子可以理解为每个数可以取任意个,能够凑出的数有哪些,貌似可以完全背包
然鹅,因为存在模k这个操作,直接跑完全背包显然是错的,跑多些容量应该是可行的,但是跑多少是难以预估的,(MLETLE警告),然后我们考虑最多取多少个物品,之后再取都没有意义,显然,让物品大小不停的加模,等到再次等于即可停止
然鹅,这样会喜提TLE,考虑继续优化,发现其实我们会多次取到同一个容量,造成了许多无意义的判断,如何简化?首先对原数组取模后排序,然后每次判断一下1单位的新的容量是否已经存在,如果存在,那说明有步长比当前容量小的已经更新过了,那么当前容量没有必要更新,这样能大大降低复杂度。最终复杂度
再说一下题解做法,其他大佬也都是这个思路,以下内容照抄题解
存在整数解,当且仅当。
那么,若可以被凑出,即 ,当且仅当。因此,答案只能是的整数
倍。但是,这样考虑$x mod kx O((n + k)logv)$
奇奇怪怪的背包代码
upd: 假做法
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int maxn=1000005;
int a[maxn];
bool vis[maxn];
int main(){
int n,k;scanf("%d%d",&n,&k);
for(int i=1;i<=n;++i)scanf("%d",&a[i]);
for(int i=1;i<=n;++i)a[i]=a[i]%k;
vis[0]=1;
sort(a+1,a+n+1);
for(int i=1;i<=n;++i){
if(vis[a[i]])continue;
bool flag=1;
for(int j=a[i];flag||j!=a[i];j=(j+a[i])%k){
flag=0;if(vis[j])continue;
for(int x=j;x<k;++x){
if(vis[x-j])vis[x]=1;
}
}
}
int ans=0;
for(int i=0;i<k;++i)if(vis[i])++ans;
printf("%d\n",ans);
for(int i=0;i<k;++i)if(vis[i])printf("%d ",i);
return 0;
}
真做法
code
#include<cstring>
#include<iostream>
using namespace std;
const int maxn = 1e6+55;
int gcd(int x, int y){
return y == 0 ? x : gcd(y, x % y);
}
bool vis[maxn];
int main(){
ios::sync_with_stdio(false);
int n , k;
cin >> n >> k;
int gc ; cin >> gc;
for(int i = 2; i <= n; ++i){
int now ; cin >> now;
gc = gcd(gc, now);
}
int now = gc % k;
while(!vis[now]){
vis[now] = 1;
now += gc;
now %= k;
}
int cnt = 0;
for(int i = 0; i <= k; ++i)if(vis[i])++cnt;
cout << cnt << endl;
for(int i = 0; i <= k; ++i)if(vis[i])cout << i << " ";
cout << endl;
return 0;
}
B. biology
首先走的点越多越好,对于a相同的点,一定由a小于他们的最大的转移,可以列出DP式子,然后我们发现该式子转移最坏情况达到了,考虑如何优化
式子中最讨厌的就是绝对值,那么我们考虑拆掉,分四种情况讨论,然后可以开四个二维树状数组维护范围最大值转移,这样复杂度可以降到,可是貌似还会TLE,如何继续优化?
其实可以发现,假如我们不考虑状态是否合法,就是那个绝对值符号是否正确,因为不合法的相当于减去了一个数,合法状态加上了一个数,最终取的一定是合法的状态,所以我们没有必要查询范围最值,直接使用全局最值是没有问题的,直接取最值即可
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
using namespace std;
const int maxn=2005;
struct node{
int x,y,a,b;
}c[maxn*maxn];
int tot,n,m;
bool cmp(node x,node y){
return x.a<y.a;
}
void In(){
scanf("%d%d",&n,&m);
tot=0;
for(int i=1;i<=n;++i)
for(int j=1;j<=m;++j)
scanf("%d",&c[++tot].a);
tot=0;
for(int i=1;i<=n;++i)
for(int j=1;j<=m;++j)
scanf("%d",&c[++tot].b);
tot=0;
for(int i=1;i<=n;++i)
for(int j=1;j<=m;++j)
{c[++tot].x=i;c[tot].y=j;}
sort(c+1,c+tot+1,cmp);
}
long long f[maxn*maxn];
void Do(){
int l=1;
while(c[l].a==0)++l;
int r=l;while(r<tot&&c[l].a==c[r+1].a)++r;
for(int i=l;i<=r;++i)f[i]=c[i].b;
long long ans=-1;
while(r<tot){
long long mx[4]={0,0,0,0};
for(int i=l;i<=r;++i){
mx[0]=max(mx[0],f[i]+c[i].x+c[i].y);
mx[1]=max(mx[1],f[i]+c[i].x-c[i].y);
mx[2]=max(mx[2],f[i]-c[i].x+c[i].y);
mx[3]=max(mx[3],f[i]-c[i].x-c[i].y);
}
int now=c[r+1].a,nx=r+1;
while(nx<=tot&&c[nx].a==now){
f[nx]=c[nx].b+max(max(c[nx].x+c[nx].y+mx[3],c[nx].x-c[nx].y+mx[2]),max(c[nx].y-c[nx].x+mx[1],-c[nx].x-c[nx].y+mx[0]));
ans=max(ans,f[nx]);
++nx;
}
l=r+1;
r=nx-1;
}
printf("%lld\n",ans);
}
int main(){
In();Do();
return 0;
}
//主函数:您礼貌吗?
C. english
这题好难啊,用到了可持久化Tire树,启发式合并等我没学过的算法,对着题解一行行敲的,先简单理解一下,等学完了那些知识再看看有没有坑填
首先暴力代码+特殊性质特判可以搞到30-50pts
然后再说正解,用单调栈处理出每个数作为最大值的区间,用和记录区间左右端点,然后对以每个x单独处理
首先说对ans1的计算,假设确定了区间一侧端点,怎么快速求出所有从该端点出发的区间的贡献?因为异或运算的存在,考虑按位处理,如果端点的值该位为1,那么另一端为0才有贡献,反之亦然。预处理出前缀和数组表示前i个数,第j位为1的个数,对于区间查询为了降低循环次数,我们用x左右两端较小的来枚举,查询另一端与该位不同的的个数,对答案有的贡献。枚举求值即可
然后对于ans2我还没理解
对每个以x为最大值的区间,同样确定了一段,查找有多少有贡献的另一端,就是查找和某段区间中几个数异或值大于,用类似前缀和的方式维护可持久化Tire树,每个数都是一个新的版本,我们只需要建一条链,剩下的查询历史版本即可。处理每个前缀的数的个数,即每个节点向下有几个有效信息。查找过程从高位向低位,如果此位为1,那与此位必须异或为1才可能贡献,如果此位为0,那么此位为异或为1的(前面与y相同)的数的个数就是贡献,然后还要继续向下累计异或为0的情况。最后用类似前缀和的方式减一下,防止负数加一个mod再计算即可。
#include<cstdio>
#include<cstring>
using namespace std;
const int mod=1e9+7;
const int maxn=2e5+25;
int max(int x,int y){return x>y?x:y;}
int a[maxn],n,opt;
int l[maxn],r[maxn],sta[maxn],top;
int sum[maxn][25],root[maxn];
long long ans1,ans2;
struct tire{
int siz[maxn*30];//当前节点子树,有多少个信息(数)
int son[maxn*30][2];//son[x][0/1] x 的两个子节点,0/1为边上维护的信息
int cnt;
void insert(int prt,int &rt,int x,int dep){//可持久化Tire树 prt历史版本根节点,rt当前状态根节点(未确定,需要传参),x插入的新信息(值),dep还需处理的深度
rt=++cnt;siz[rt]=siz[prt];//开新点,个数记录历史版本
if(dep<0){//处理完毕,加上本次贡献返回
++siz[rt];
return;
}
int tmp=(x>>dep)&1;
son[rt][tmp^1]=son[prt][tmp^1];//当前没有的点,继承历史版本
insert(son[prt][tmp],son[rt][tmp],x,dep-1);//向下
siz[rt]=siz[son[rt][0]]+siz[son[rt][1]];//统计个数
return;
}
int query(int x,int y,int pos){//查询在1-pos区间内有多少数异或x大于y,即有贡献的个数
int u=root[pos],ret=0;//ret统计答案,u为当前点
for(int i=20;i>=0;--i){//从高位向低位找
int a=(x>>i)&1;
int b=(y>>i)&1;
if(b==0){//如果y此位为0,那么此位为异或为1的(前面与y相同)的数的个数就是贡献
ret+=siz[son[u][a^1]];//记录当前贡献
u=son[u][a];//继续查找与y此位也相同,大于y的个数
}
else u=son[u][a^1];//如果y此位为1,那此位必须异或为1才可能贡献,向下求解
if(!u)break;
}
return ret;
}
}t;
void solve(int x,int l,int r){
if(x-l<r-x){
for(int i=l;i<=x;++i){
for(int j=20;j>=0;--j){
if((a[i]>>j)&1)ans1=(ans1+1ll*(1<<j)*(r-x+1-sum[r][j]+sum[x-1][j])%mod*a[x]%mod)%mod;
else ans1=(ans1+1ll*(1<<j)*(sum[r][j]-sum[x-1][j])%mod*a[x]%mod)%mod;
}
ans2=(ans2+1ll*(t.query(a[i],a[x],r)-t.query(a[i],a[x],x-1)+mod)*a[x]%mod)%mod;
}
}else{
for(int i=x;i<=r;++i){
for(int j=20;j>=0;--j){
if((a[i]>>j)&1)ans1=(ans1+1ll*(1<<j)*(x-l+1-sum[x][j]+sum[l-1][j])%mod*a[x]%mod)%mod;
else ans1=(ans1+1ll*(1<<j)*(sum[x][j]-sum[l-1][j])%mod*a[x]%mod)%mod;
}
ans2=(ans2+1ll*(t.query(a[i],a[x],x)-t.query(a[i],a[x],l-1)+mod)*a[x]%mod)%mod;
}
}
}
int main(){
//freopen("english3.in","r",stdin);
//freopen("english.out","w",stdout);
scanf("%d%d",&n,&opt);
for(int i=1;i<=n;++i)scanf("%d",&a[i]),t.insert(root[i-1],root[i],a[i],20);
//单调栈维护x为最大值的区间,l[i],r[i]为区间左右端点
for(int i=1;i<=n;++i){
while(a[i]>=a[sta[top]]&&top)r[sta[top]]=i-1,--top;
l[i]=sta[top]+1;
sta[++top]=i;
}
while(top)r[sta[top]]=n,--top;
//sum前缀和,sum[i][j]前i个数 从低向位第j位为1的有几个
for(int i=1;i<=n;++i){
for(int j=0;j<=20;++j){
if(1&(a[i]>>j))sum[i][j]=sum[i-1][j]+1;
else sum[i][j]=sum[i-1][j];
}
}
for(int i=1;i<=n;++i)solve(i,l[i],r[i]);
if(opt&1)printf("%lld\n",ans1);
if(opt&2)printf("%lld\n",ans2);
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】