2025/3/31
赛时
T1 很唐,哈希水题
搞搞就过了
期望得分 \(\text{100}\)
8:16
后面的题看了一遍
都很难受
要不先干 T2?
这山本数据实在是过于离谱了
我感觉判个 \(\text{Impossible!}\) 都能得很多
所以说其实我们可以先搞一个归并逆序对
然后合法再想办法
\(n^2\) 暴力的话大概也就只有 \(\text{20 pts}\)
思考正解
数据 \(n≤10^6,k≤10^{12}\)
那我们大概需要一个 \(n\log n\) 的复杂度
仔细读题面发现一个细节
a 数组互不相同
那能不能搞一下呢
算了不管了先打个暴力再说
#include<bits/stdc++.h>
using namespace std;
const int N=100098;
typedef long long ll;
ll k,cnt=0;
int n,a[N],b[N],ltmp[N],rtmp[N];
void MSORT(int l,int r){
if(l>=r)return;
int mid=(l+r)>>1;
MSORT(l,mid),MSORT(mid+1,r);
for(int i=l;i<=mid;i++)ltmp[i]=b[i];
for(int i=mid+1;i<=r;i++)rtmp[i]=b[i];
int ml=l,mr=mid+1,idx=l-1;
while(ml<=mid&&mr<=r){
if(ltmp[ml]<rtmp[mr])b[++idx]=ltmp[ml++];
else b[++idx]=rtmp[mr++],cnt+=mid-ml+1;
}
while(ml<=mid)b[++idx]=ltmp[ml++];
while(mr<=r)b[++idx]=rtmp[mr++];
}
int main(){
scanf("%d%lld",&n,&k);
for(int i=1;i<=n;i++)scanf("%d",&a[i]),b[i]=a[i];
MSORT(1,n);
if(k>cnt)puts("Impossible!"),exit(0);
else if(k==cnt){
for(int i=1;i<=n;i++)printf("%d ",a[i]);
exit(0);
}
for(int i=1;i<n;i++) for(int j=1;j<=n-i;j++){
if(a[j]>a[j+1]){
swap(a[j],a[j+1]),--k;
if(k==0){
for(int i=1;i<=n;i++)printf("%d ",a[i]);
exit(0);
}
}
}
return 0;
}
期望得分 \(\text{120}\)
9:31
决定打一个 T3 暴力代码
这题看起来很离谱
求 \(\max(a_x+a_y,b_x+b_y)\)
我们分析一下
首先这题有两个操作:插入和删除
我们首先要解决的一个问题就是:怎么查找?
我觉得这个其实开一个 \(\text{unordered\_map}\) 哈希一下就可以了
比如说 \(a*1e9+b\)
这样就可以实现查找删除 \(O(1)\) 了
但下一个问题就是题里说的:咋找这个值呢?
暴力的话我们可能要枚举两个集合中所有元素
能过 \(20\) 就很不错了
如果我们开一个 \(\text{Map}\) 呢
能快点吗?
我们可以搞一个当前最优答案
整一个类似最优性剪枝的抽象东西
基本它是按照 \(A\) 自动排好序的
加上 4s
今日我一个 \(map\) 夺得二十大点,不是问题
加一个小小的优化
就是我们记录上一次的答案
如果是插入的话
我们直接卡住这个答案枚举即可
#include<bits/stdc++.h>
#ifdef ONLINE_JUDGE
#define getchar getchar_unlocked
#define putchar putchar_unlocked
#endif
using namespace std;
typedef long long ll;
map<ll,int> s[2];
const ll base=1e9+1;
inline ll gh(ll a,ll b){return a*base+b;}
inline void rh(ll tmp,ll &a,ll &b){a=tmp/base,b=tmp%base;}
ll worst;
inline int Scan(){
int ans=0;
char k=getchar();
while(!isdigit(k))k=getchar();
while(isdigit(k))ans=ans*10+k-'0',k=getchar();
return ans;
}
stack<int> t;
inline void Print(ll k){
while(k)t.push(k%10),k/=10;
while(!t.empty())putchar(t.top()+'0'),t.pop();
putchar('\n');
}
inline void Neg(){
putchar('-'),putchar('1'),putchar('\n');
}
int main(){
int T,opt,d,a,b,cnt[2]={};
ll t1a,t1b,t2a,t2b;
T=Scan(),worst=base<<1;
while(T--){
opt=Scan(),d=Scan(),a=Scan(),b=Scan();
if(opt==0){
--s[d][gh(a,b)],--cnt[d];
if(cnt[0]==0||cnt[1]==0){Neg();continue;}
if(s[d][gh(a,b)]>0){Print(worst);continue;}
worst=base<<1;
for(auto i=s[0].begin();i!=s[0].end();i++){
if(i->second==0)continue;
for(auto j=s[1].begin();j!=s[1].end();j++){
if(j->second==0)continue;
rh(i->first,t1a,t1b),rh(j->first,t2a,t2b);
//printf("%lld %lld %lld %lld\n",t1a,t2a,t1b,t2b);
if(t1a+t2a>=worst)break;
worst=min(worst,max(t1a+t2a,t1b+t2b));
}
}
}else if(opt==1){
++s[d][gh(a,b)],++cnt[d];
if(cnt[0]==0||cnt[1]==0){Neg();continue;}
if(s[d][gh(a,b)]>1){Print(worst);continue;}
t1a=a,t1b=b;
for(auto i=s[d^1].begin();i!=s[d^1].end();i++){
if(i->second==0)continue;
rh(i->first,t2a,t2b);
//printf("%lld %lld %lld %lld\n",t1a,t2a,t1b,t2b);
if(t1a+t2a>=worst)break;
worst=min(worst,max(t1a+t2a,t1b+t2b));
}
}
Print(worst);
}
return 0;
}
//-1
//105
//55
//43
//36
//48
期望得分 \(\text{140}\)
\(\text{10:59}\)
T4 20 区间dp
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll Mod=1e9+7;
const int N=1009;
ll dp[N][N];
int n,q;
int main(){
scanf("%d%d",&n,&q);
for(int i=1;i<=n;i++)scanf("%lld",&dp[i][1]);
for(int l=2;l<=n;l++){
for(int i=1;i+l-1<=n;i++){
for(int sep=i+1;sep<=i+l-1;sep++){
dp[i][l]=max(dp[i][sep-i]+dp[sep][i+l-sep]*2,dp[i][l]);
}
}
}
int l,r;
while(q--){
scanf("%d%d",&l,&r);
printf("%lld\n",dp[l][r-l+1]%Mod);
}
return 0;
}
期望得分 \(\text{160}\)
\(\text{11:08}\)
赛后补题部分
T1
哈希一本通练习原题
难度黄
#include<bits/stdc++.h>
using namespace std;
//Data Part
typedef long long ll;
const ll Mod1=1e9+7,Mod2=1e9+9,Base=31;
const int N=2000099;
int n,mid;
char k[N];
ll h1[N],h2[N],ans1=-1,ans2=-1,lim=-1,rim=-1,Base1[N],Base2[N];
//Merge Part
inline void Add(ll lh1,ll lh2,ll rh1,ll rh2,int rlen,ll &Ans1,ll &Ans2){
Ans1=(Base1[rlen]*lh1%Mod1+rh1)%Mod1;
Ans2=(Base2[rlen]*lh2%Mod2+rh2)%Mod2;
}
inline ll Cut1(int l,int r){return ((h1[r]-h1[l-1]*Base1[r-l+1]%Mod1)%Mod1+Mod1)%Mod1;}
inline ll Cut2(int l,int r){return ((h2[r]-h2[l-1]*Base2[r-l+1]%Mod2)%Mod2+Mod2)%Mod2;}
int main(){
scanf("%d%s",&n,k+1),mid=(n+1)>>1,Base1[0]=Base2[0]=1;
if(n%2==0)puts("NOT POSSIBLE"),exit(0);
for(int i=1;i<=n;i++){
h1[i]=(h1[i-1]*Base+(k[i]-'A'))%Mod1;
h2[i]=(h2[i-1]*Base+(k[i]-'A'))%Mod2;
Base1[i]=Base1[i-1]*Base%Mod1;
Base2[i]=Base2[i-1]*Base%Mod2;
}
ll lans1,lans2,rans1,rans2,tmplim,tmprim;
for(int i=1;i<=n;i++){
if(i<mid){
rans1=Cut1(mid+1,n),rans2=Cut2(mid+1,n),tmplim=mid+1,tmprim=n;
Add(Cut1(1,i-1),Cut2(1,i-1),Cut1(i+1,mid),Cut2(i+1,mid),mid-i,lans1,lans2);
}else if(i==mid){
lans1=Cut1(1,i-1),lans2=Cut2(1,i-1),tmplim=1,tmprim=mid-1;
rans1=Cut1(i+1,n),rans2=Cut2(i+1,n);
}else{
lans1=Cut1(1,mid-1),lans2=Cut2(1,mid-1),tmplim=1,tmprim=mid-1;
Add(Cut1(mid,i-1),Cut2(mid,i-1),Cut1(i+1,n),Cut2(i+1,n),n-i,rans1,rans2);
}
//printf("%d %lld %lld %lld %lld\n",i,lans1,rans1,lans2,rans2);
if(lans1!=rans1||lans2!=rans2)continue;
if(ans1==-1||ans2==-1)ans1=lans1,ans2=lans2,lim=tmplim,rim=tmprim;
else if(ans1==lans1)continue;
else puts("NOT UNIQUE"),exit(0);
}
if(ans1==-1||ans2==-1)puts("NOT POSSIBLE"),exit(0);
for(int i=lim;i<=rim;i++)putchar(k[i]);
return 0;
}
T2
题解说这题首先有一个思路
我们先搞出 \(i\) 轮完整的冒泡
然后在暴力的搞剩下的几个 \(num\)
那么现在我们就有了这么步骤
- 以较小的时间复杂度模拟一轮冒泡
- 判断当前还能搞几轮冒泡,需要几次 \(\text{swap}\)
- 得出剩下几个操作数量来暴力搞
然后我们下一个问题就是: 如何模拟跑完后的数组
首先有一部分的东西是很唐的
他们可能 \(k\) 轮中每轮都被压一次
这种很简单
直接向左移动 \(k\) 就可以了
剩下的呢
根据我们上面的结论
我们
我们看个图

图中带红圈的就是我们会 "向后推" 的位置
我们可以发现几个规律
- 我们能 “向后推” 的位置都是单调递增的
- 向后推的过程会被下一个比她大的数卡住
- 如果一个数的左边有比它大的数,那他就不能向后推
- 一轮中一个数至多只被一个“向后推”的数撞到
我们这些东西就可以求出 \(k\) 轮冒泡的最小代价
首先,我们发现向后退的并不好处理
因为他们涉及到 "被卡" 这个恶心的东西。。。
所以我们可以统计每一个数字会被“撞”多少次
这个数是多少呢?
首先,他不会大于 \(k\)
谁家好人一轮就被创两次啊
其次,他不会大于这个数左边比他大的数的个数
因为只有他们才能创到他并轧过去
而我们当前的数如果向后推的话
他肯定无法压掉比它大的数
也就是说他左边比它大的数的数量不会增多
然后我们一波并不算玄学的推论
他被创到并被轧得次数就是 \(\min(k,cnt)\)
其中 \(cnt=\sum_{it=1}^{i-1} [k[it]>k[i]]\)
而容易看出
我们每个点被轧的次数
其实就是 \(\text{swap}\) 的次数
所以我们用树状数组维护每个点的 \(\text{cnt}\)
然后枚举 \(k\) 的值
得出可行的轮数
用玄学方式整出搞好的数组
然后最后一轮直接暴力
最后一个问题
怎么模拟?
首先我们发现有的数字很唐
他们在这 \(k\) 轮之中都会被压一次
然后他们的位置就是原位置减去 \(k\)
剩下的呢
由于他们左边根据定义已经没有比他们大的了
所以直接排序往里填
最后一个暴力
完结撒花!
难度绿至蓝
#include<bits/stdc++.h>
using namespace std;
//Data Part
typedef long long ll;
const int N=1000009;
int n,tmp[N],a[N],cnt[N],Round; ll k;
namespace Merge_Sort{
int ltmp[N],rtmp[N];
ll ans=0;
void Merge(int l=1,int r=n){
if(l==r)return;
int mid=(l+r)>>1;
Merge(l,mid),Merge(mid+1,r);
for(int i=l;i<=mid;i++)ltmp[i]=tmp[i];
for(int i=mid+1;i<=r;i++)rtmp[i]=tmp[i];
int L=l,R=mid+1,idx=l-1;
while(L<=mid&&R<=r){
if(ltmp[L]<rtmp[R])tmp[++idx]=ltmp[L++];
else tmp[++idx]=rtmp[R++],ans+=mid-L+1;
}
while(L<=mid)tmp[++idx]=ltmp[L++];
while(R<=r)tmp[++idx]=rtmp[R++];
}
void Check(){
Merge();
if(ans<k)puts("Impossible!"),exit(0);
}
}
namespace Tree{
int t[N]={};
void Modify(int u){while(u<=n)t[u]++,u+=(u&-u);}
int Query(int u){int ans=0;while(u>=1)ans+=t[u],u-=(u&-u);return ans;}
void Cnt(){
for(int i=1;i<=n;i++)cnt[i]=i-1-Query(a[i]),Modify(a[i]);
}
}
namespace Binary{
bool Check(int x,bool op){
ll need=0;
for(int i=1;i<=n;i++)need+=min(x,cnt[i]);
if(op)k-=need;
return need<=k;
}
void Solve(int l=1,int r=n){
int mid;
while(l<r){
mid=(l+r+1)>>1;
if(Check(mid,false))l=mid; else r=mid-1;
}
Round=l,Check(l,true);
}
}
namespace Fill{
vector<int> vec;
void Solve(){
for(int i=1;i<=n;i++) tmp[i]=0;
for(int i=1;i<=n;i++) if(cnt[i]>=Round) tmp[i-Round]=a[i]; else vec.push_back(a[i]);
sort(vec.begin(),vec.end());
int idx=1;
for(int i:vec){while(tmp[idx])++idx; tmp[idx]=i;}
}
}
namespace Brute{
void Solve(){
for(int i=1;i<n;i++) if(tmp[i]>tmp[i+1]){
if(k==0)return;
--k,swap(tmp[i],tmp[i+1]);
}
}
}
int main(){
scanf("%d%lld",&n,&k);
for(int i=1;i<=n;i++)scanf("%d",&tmp[i]),a[i]=tmp[i];
Merge_Sort::Check();
Tree::Cnt();
Binary::Solve();
Fill::Solve();
Brute::Solve();
for(int i=1;i<=n;i++)printf("%d ",tmp[i]);
}

浙公网安备 33010602011771号