『模拟赛』暑假集训CSP提高模拟17
Rank
A. 符号化方法初探
挺水的,不过赛时想了错解。
赛时做法是都塞到一个 set 里一遍推过去,如果比上一个小就 lower_bound 找一个最接近差的数加上,不过它存在比较大的问题。
首先全是负判不了,会进入死循环;其次用 map 存下标也会出现存在两个相同的值,一个替换另一个后值更改,此时找到的下标对应的值并不是我们想要的值。
但是还是对他抱一些希望,如果有能调出来的大佬请帮忙看下感谢qwq。
赛时代码
更改前
#include<bits/stdc++.h>
#define fo(x,y,z) for(register int (x)=(y);(x)<=(z);(x)++)
#define fu(x,y,z) for(register int (x)=(y);(x)>=(z);(x)--)
using namespace std;
typedef long long ll;
#define lx int
inline lx qr()
{
char ch=getchar();lx x=0,f=1;
for(;ch<'0'||ch>'9';ch=getchar())if(ch=='-')f=-1;
for(;ch>='0'&&ch<='9';ch=getchar())x=(x<<3)+(x<<1)+(ch^48);
return x*f;
}
#undef lx
#define qr qr()
const int Ratio=0;
const int N=1e5+5;
int n,m;
ll a[N];
ll q1[N],q2[N];
map<ll,ll>mp;
set<ll>st;
set<ll>::iterator it;
namespace Wisadel
{
short main()
{
// freopen(".in","r",stdin),freopen(".out","w",stdout);
n=qr;
fo(i,1,n) a[i]=qr,st.insert(a[i]),mp[a[i]]=i;
fo(i,2,n)
{
while(a[i]<a[i-1])
{
it=st.lower_bound(a[i-1]-a[i]);
if(it==st.end()) --it;
q1[++m]=mp[*it],q2[m]=i;
st.erase(a[i]);
a[i]+=*it;
st.insert(a[i]);
mp[a[i]]=i;
}
}
printf("%d\n",m);
fo(i,1,m)
printf("%lld %lld\n",q1[i],q2[i]);
return Ratio;
}
}
int main(){return Wisadel::main();}
更改后
#include<bits/stdc++.h>
#define fo(x,y,z) for(register int (x)=(y);(x)<=(z);(x)++)
#define fu(x,y,z) for(register int (x)=(y);(x)>=(z);(x)--)
using namespace std;
typedef long long ll;
#define lx int
inline lx qr()
{
char ch=getchar();lx x=0,f=1;
for(;ch<'0'||ch>'9';ch=getchar())if(ch=='-')f=-1;
for(;ch>='0'&&ch<='9';ch=getchar())x=(x<<3)+(x<<1)+(ch^48);
return x*f;
}
#undef lx
#define qr qr()
const int Ratio=0;
const int N=1e5+5;
int n,m;
ll a[N];
ll q1[N],q2[N];
map<ll,ll>mp;
set<ll>st;
set<ll>::iterator it;
namespace Wisadel
{
short main()
{
// freopen(".in","r",stdin),freopen(".out","w",stdout);
n=qr;
ll maxx=-1e9,minn=1e9;
fo(i,1,n) a[i]=qr,st.insert(a[i]),mp[a[i]]=i,maxx=max(maxx,a[i]),minn=min(minn,a[i]);
if(maxx+minn>0)
{
fo(i,2,n)
{
while(a[i]<a[i-1])
{
it=st.lower_bound(a[i-1]-a[i]);
if(it==st.end()) --it;
q1[++m]=mp[*it],q2[m]=i;
st.erase(a[i]);
a[i]+=*it;
st.insert(a[i]);
mp[a[i]]=i;
}
}
}
else
{
fu(i,n-1,1)
{
// cout<<i<<' '<<i+1<<":"<<a[i]<<' '<<a[i+1]<<endl;
while(a[i]>a[i+1])
{
// cout<<i<<' '<<a[i]<<' '<<a[i+1]<<endl;
it=st.lower_bound(a[i+1]-a[i]);
if(a[i+1]-a[i]!=*it&&it!=st.begin()) --it;
// cout<<"$%^&*(&^%$#)"<<*it<<' '<<mp[*it]<<endl;
q1[++m]=mp[*it],q2[m]=i;
st.erase(a[i]);
a[i]+=*it;
st.insert(a[i]);
mp[a[i]]=i;
}
}
}
printf("%d\n",m);
fo(i,1,m)
printf("%lld %lld\n",q1[i],q2[i]);
return Ratio;
}
}
int main(){return Wisadel::main();}
正解是若全不为负就 n-1 次碾过去,每次让 i 加上前一个的值;若全不为正就倒序同上;否则比较极值的绝对值大小,最大值的较大就让所有除最大值外的数加上最大值,然后同情况 1,最小值的较大就让所有除最小值外的数加上最小值,然后同情况 2。
点击查看代码
#include<bits/stdc++.h>
#define fo(x,y,z) for(register int (x)=(y);(x)<=(z);(x)++)
#define fu(x,y,z) for(register int (x)=(y);(x)>=(z);(x)--)
using namespace std;
typedef long long ll;
#define lx int
inline lx qr()
{
char ch=getchar();lx x=0,f=1;
for(;ch<'0'||ch>'9';ch=getchar())if(ch=='-')f=-1;
for(;ch>='0'&&ch<='9';ch=getchar())x=(x<<3)+(x<<1)+(ch^48);
return x*f;
}
#undef lx
#define qr qr()
const int Ratio=0;
const int N=2e5+5;
const int mod=998244353;
int n,m;
struct rmm
{
ll x,id;
}a[N];
bool cmp(rmm a,rmm b){return a.x<b.x;}
namespace Wisadel
{
short main()
{
// freopen(".in","r",stdin),freopen(".out","w",stdout);
n=qr;
fo(i,1,n) a[i].x=qr,a[i].id=i;
sort(a+1,a+1+n,cmp);
if(a[1].x>=0)
{
printf("%d\n",n-1);
fo(i,1,n-1) printf("%d %d\n",i,i+1);
}
else if(a[n].x<=0)
{
printf("%d\n",n-1);
fu(i,n,2) printf("%d %d\n",i,i-1);
}
else
{
printf("%d\n",2*n-2);
if(abs(a[1].x)<=a[n].x)
{
fo(i,1,n) if(i!=a[n].id) printf("%d %d\n",a[n].id,i);
fo(i,1,n-1) printf("%d %d\n",i,i+1);
}
else
{
fo(i,1,n) if(i!=a[1].id) printf("%d %d\n",a[1].id,i);
fu(i,n,2) printf("%d %d\n",i,i-1);
}
}
return Ratio;
}
}
int main(){return Wisadel::main();}
B. 无标号 Sequence 构造
一道非常有学习意义的题。
一眼脑残题,然后看到数据范围 \(n\le 3000\) 蒙了,没想到什么好办法,所以还是 \(\mathcal{O(n^3)}\) 硬莽拿下 50pts。
正解是随机一个 \(1\times n\) 的矩阵,这样就缩小了矩阵相乘的规模,从而将复杂度降低到 \(\mathcal{O(N^2)}\) 级别。
点击查看代码
#include<bits/stdc++.h>
#define fo(x,y,z) for(register int (x)=(y);(x)<=(z);(x)++)
const int Ratio=0;
const int N=3e3+5;
const int mod=998244353;
int T,n;
int a[N][N],b[N][N],c[N][N],d[2][N],e[2][N],f[2][N],g[2][N];
namespace Wisadel
{
short main()
{
scanf("%d",&T);srand(time(0));
while(T--)
{
scanf("%d",&n);bool can=1;
fo(i,1,n) fo(j,1,n) scanf("%d",&a[i][j]);
fo(i,1,n) fo(j,1,n) scanf("%d",&b[i][j]);
fo(i,1,n) fo(j,1,n) scanf("%d",&c[i][j]);
fo(i,1,n) d[1][i]=rand(),e[1][i]=0,f[1][i]=0,g[1][i]=0;
fo(j,1,n) fo(k,1,n) e[1][j]=(e[1][j]+1ll*d[1][k]*a[k][j]%mod)%mod;
fo(j,1,n) fo(k,1,n) f[1][j]=(f[1][j]+1ll*e[1][k]*b[k][j]%mod)%mod;
fo(j,1,n) fo(k,1,n) g[1][j]=(g[1][j]+1ll*d[1][k]*c[k][j]%mod)%mod;
fo(i,1,n) if(f[1][i]!=g[1][i]){can=0;break;}
printf(can?"Yes\n":"No\n");
}
return Ratio;
}
}
int main(){return Wisadel::main();}
C. 无标号 Multiset 构造
需要先打半个小时表预处理出来不同的 m 值,所以只拿了 5pts 的样例分。
D. 有限制的构造
也很有学习意义的一题。
如果不看数据范围,是一个很经典的二维费用背包问题,\(f_{i,j,k}\) 表示到第 \(i\) 个游戏,画质和为 \(j\),不可玩度为 \(k\) 最多选几个游戏,最终答案为 \(f_{n,A,B}\),当然第一维完全可以省略。
但是,这道题两个费用的上线都是 \(10^4\) 级别的,空间上就算省掉了一维复杂度还是 \(\mathcal{O(n^2)}\),就算开 short 也无法承受,时间复杂度为 \(\mathcal{O(nV^2)}\) 同样无法实现。
所以需要转换思路。
首先一种,因为 \(n\le 80\) 范围极小,所以我们可以选择退火,(但是不会所以不考虑。
第二,我们可以交换一维 dp 数组的维度,将范围较小的值域计入一维,将范围较大的费用计入值域,此时我们的 dp 数组的含义变成了:\(f_{i,j,k}\) 表示到第 \(i\) 个游戏,玩了 \(j\) 个,画质和为 \(k\) 时的最小不可玩度。这样一来,我们的空间复杂度和时间复杂度都变为了 \(\mathcal{O(n^2V)}\),最后统计答案只需要倒序遍历一遍 \(j\) 维找到值满足 \(\le B\) 的即可。
还要注意的一点,我们可以最后超出界限一个游戏,想象一下就是:背包目前塞不下一个物品,我们可以敞口再放一个。不过这样的前提是 \(n\) 个游戏没有全部都玩(致敬 lxyt。
点击查看代码(未优化,开 short 过的)
#include<bits/stdc++.h>
#define fo(x,y,z) for(register int (x)=(y);(x)<=(z);(x)++)
#define fu(x,y,z) for(register int (x)=(y);(x)>=(z);(x)--)
using namespace std;
typedef long long ll;
#define lx int
inline lx qr()
{
char ch=getchar();lx x=0,f=1;
for(;ch<'0'||ch>'9';ch=getchar())if(ch=='-')f=-1;
for(;ch>='0'&&ch<='9';ch=getchar())x=(x<<3)+(x<<1)+(ch^48);
return x*f;
}
#undef lx
#define qr qr()
const int Ratio=0;
const int N=1e4+5;
int n,A,B;
short a[N],b[N],f[88][88][N];
namespace Wisadel
{
short main()
{
// freopen(".in","r",stdin),freopen(".out","w",stdout);
n=qr,A=qr,B=qr;
fo(i,1,n) a[i]=qr,b[i]=qr;
memset(f,0x3f,sizeof f);
fo(i,0,n) f[i][0][0]=0;
fo(i,1,n) fo(j,1,i) fo(k,0,A)
{
if(k>=a[i]) f[i][j][k]=min(f[i][j][k],short(f[i-1][j-1][k-a[i]]+b[i]));
f[i][j][k]=min(f[i][j][k],f[i-1][j][k]);
}
fu(i,n,0) fo(j,0,A) if(f[n][i][j]<=B)
{
printf("%d\n",i+(i!=n));
return Ratio;
}
return Ratio;
}
}
int main(){return Wisadel::main();}
末
这次也是收获很多的一场模拟赛啊,能学到很多专题里学不到的小细节。
博客写到 T4 差点坠机了,还好 yuen 告诉我博客园有自动备份,感谢博客园!同时感谢在榜首把我的分块莫队挂了两天!
赛时一直受困于在线测评的 sb 机制:超过给定内存的一半自动 RE,导致我卡在 T2 一个小时,最后还被迫改小了数组,(虽然改不改一个分。
完结撒花~
另外
\(\Huge{七夕节快乐!}\)
\(\small{能在学 OI 时找到另一半吗www}\)