前言:整场的题目质量比较高,虽然之前做过一部分题,但还是被薄纱了
Changing the Sequences
大意:
给定两个数组a,b,长度都为n,元素都介于1-m之间
定义一次操作如下:
构造一个1-m的排列p,对于对于a中的每一个元素,,得到a'
只进行一次该操作,要求使a'与b的元素不相同的位置尽可能少。并求出满足条件的字典序最小的a'
思路:
显然一次操作的本质是构造一个a到a'的单射。我们可以直接考虑映射的值。将ai与对应的bi连边,表示如果ai的映射是bi,可以造成一个贡献。我们希望最终的贡献尽可能多。显然边的权值可以累加。
所以本质上我们就是在一个二分图上寻找匹配,使得匹配边的权值之和尽可能大。也就是最大权匹配问题。如果不考虑字典序最小的话,我们可以直接用KM来解决这个问题。时间复杂度就是m^3
现在想想如何让字典序最小。说来可以,这其实是一种不算罕见的套路,但是赛时我们队还是全体宕机,但事后看看,并没有想象中的那么不可做。
显然字典序最小的前提是保持权值和最大,然后我们去修改可以连边,使得a数组中靠前的数字尽可能去匹配尽可能小的数字。为了做到这一点,我们先将a数组的元素按位置先后从小到大赋值(因为其本身的元素大小并没有什么用,我们关心的只是元素的位置)。
然后无脑跑一遍KM,得到最大的匹配权值和ans。然后我们尝试修改匹配方案。
外层遍历更新后的a数组,内层遍历映射的域,分别记为i,j。尝试将i的映射变成j,我们需要验证i->j的权值贡献加上图的其他部分的贡献与ans相同。那么将i连向其他点的权值置为0,再将其他点连向j的权值置为0,重新跑一遍KM,加上i到j的权值贡献,即可。如果和与ans相同的话,代表这个匹配是可以的。这里有贪心的成分在,但是因为我们是要字典序最小,前缀越小约好,所以可以贪心。匹配成功后,我们就将两个点从图中去除即可。否则恢复成原来的状态。
code
#include<bits/stdc++.h>
using namespace std;
#define ll int
#define endl '\n'
const int INF=0x3f3f3f3f;
const int N=1e5+10;
int n,m,match[N],pre[N];
bool vis[N];
int favor[70][70];
int val1[N],val2[N],slack[N];
ll a[N],b[N];
ll mp[N],cn;
ll To[70];
ll Pre[70][70];
ll change[70];
ll VIS[70];
void bfs(int p)
{
memset(pre,0,sizeof pre);
memset(slack,INF,sizeof slack);
match[0]=p;
int x=0,nex=0;
do{
vis[x]=true;
int y=match[x],d=INF;
// 瀵逛簬褰撳墠鑺傜偣y锛宐fs鏈夎繛杈圭殑涓嬩竴鐐?
for(int i=1;i<=m;i++)
{
if(!vis[i])
{
if(slack[i]>val1[y]+val2[i]-favor[y][i])
{
slack[i]=val1[y]+val2[i]-favor[y][i];
pre[i]=x;
}
if(slack[i]<d)
{
d=slack[i];
nex=i;
}
}
}
for(int i=0;i<=m;i++)
{
if(vis[i])
val1[match[i]]-=d,val2[i]+=d;
else
slack[i]-=d;
}
x=nex;
}while(match[x]);
while(x)
{
match[x]=match[pre[x]];
x=pre[x];
}
}
ll KM()
{
memset(match,0,sizeof match);
memset(val1,0,sizeof val1);
memset(val2,0,sizeof val2);
for(int i=1;i<=m;i++)
{
memset(vis,false,sizeof vis);
bfs(i);
}
ll ans=0;
for(int i=1;i<=m;++i) ans+=favor[match[i]][i];
return ans;
}
int main()
{
cin>>n>>m;
for(int i=1;i<=n;++i) cin>>a[i];
for(int i=1;i<=n;++i) cin>>b[i];
for(int i=1;i<=n;++i)
{
if(!mp[a[i]]) mp[a[i]]=++cn;
a[i]=mp[a[i]];
}
for(int i=1;i<=n;++i)
{
favor[a[i]][b[i]]++;
}
for(int i=1;i<=m;++i)
{
for(int j=1;j<=m;++j) Pre[i][j]=favor[i][j];//原始数组
}
ll ans=KM();//全局最优解
ll sum=0;
for(int i=1;i<=m;++i)
{
for(int j=1;j<=m;++j)
{
if(VIS[j]) continue;
ll th_val=favor[i][j];
for(int s=1;s<=m;++s)
{
favor[i][s]=0;
favor[s][j]=0;//除去
}
if(th_val+sum+KM()==ans)//代表匹配成功,我们修改它
{
VIS[j]=1;
change[i]=j;
sum+=th_val;//sum记录之前已经匹配过的映射的权值贡献和
break;
}
for(int s=1;s<=m;++s)
{
if(s>=i) favor[s][j]=Pre[s][j];//前面都是已匹配过的点
if(VIS[s]) continue;//已经连接过
favor[i][s]=Pre[i][s];
}
}
}
for(int i=1;i<=n;++i) cout<<change[a[i]]<<' ';
return 0;
}
Determine The Fluctuation Bonus
大意:
给定n个人,每一个人有一个初始分数为0.q次操作,每次操作给定id,a,第id个人加上a分。每次操作之后所有人按分数重新排序,获得排名变化的绝对值的贡献。
求q次操作之后每一个人的贡献。
思路:
看着难,实际也没那么不可做,还是太怂了
每一个人的分数可以分成两个部分:自己操作带来的贡献,以及别人操作对自己带来的贡献。第一个值其实比较好维护,我们只需要记录每一个人的分数,然后每一次操作的时候维护一下排名,贡献就是排名的变化了,具体实现用一个树状数组T1来维护分数。第二个部分可以用另一个树状数组T2维护。每一次操作之后,分数从pre变成了now,那么只有分数区间在[pre,now-1]之间的人才会获得贡献,并且贡献为1,简单做一个差分即可。
最后一个问题就是如何将两者相加。让第二个树状数组T2记录每一个分数累计获得的贡献。每次操作的时候,对于id,我们然其贡献加上T2(pre),再减去T2(now),即可。因为id之前一直在pre的位置,所以加上这个位置带来的贡献。但是下一次又轮到id操作的时候,它并不是从一开始就待在当前的now这个位置的,所以我们先提前减去T2(now),下一次就可以放心加上对应的值了,实现起来也比较容易。
同时数据值域比较大,我们还要提前离线下来把数据离散化。
写起来有点绕,细节比较多。
code
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define low(x) x&(-x)
#define endl '\n'
const ll N=2e5+10;
const ll M=2e5;
struct Tree
{
ll tr[N];
void add(ll x,ll y)
{
while(x<=M)
{
tr[x]+=y;
x+=low(x);
}
}
ll sum(ll x)
{
ll ans=0;
while(x)
{
ans+=tr[x];
x-=low(x);
}
return ans;
}
ll Gt_sum(ll l,ll r)
{
return sum(r)-sum(l-1);
}
void upd(ll l,ll r,ll val)
{
add(l,val);add(r+1,-val);
}
}T1,T2;
ll n,q;
map<ll,ll> mp;
struct ty
{
ll id,val;
}mas[N];
ll pos[N];
set<ll> st;
int idx=0;
ll ans[N];
void solve()
{
cin>>n>>q;
st.insert(0);
for(int i=1;i<=q;++i)
{
cin>>mas[i].id>>mas[i].val;
st.insert(pos[mas[i].id]);
pos[mas[i].id]+=mas[i].val;
st.insert(pos[mas[i].id]);
}
for(auto s:st) mp[s]=++idx;
for(int i=0;i<=n;++i) pos[i]=0;
// for(auto s:st) cout<<s<<" ";
// cout<<endl;
T2.add(mp[0],n);
for(int i=1;i<=q;++i)
{
ll id=mas[i].id;ll val=mas[i].val;
ll pre_pos=mp[pos[id]],now_pos=mp[pos[id]+val];
pos[id]+=val;
ll pre_lnk=1+T2.Gt_sum(pre_pos+1,M);
// T2.upd(pre_pos,pre_pos,-1);T2.upd(now_pos,now_pos,1);
T2.add(pre_pos,-1);T2.add(now_pos,1);
ll now_lnk=1+T2.Gt_sum(now_pos+1,M);
// cout<<id<<" "<<pre_lnk<<' '<<now_lnk<<endl;
ans[id]+=abs(pre_lnk-now_lnk);
ans[id]+=T1.sum(pre_pos);
ll mi=min(pre_pos,now_pos);ll ma=max(pre_pos,now_pos)-1;
T1.upd(mi,ma,1);
ans[id]-=T1.sum(now_pos);
}
// for(int i=1;i<=n;++i) cout<<ans[i]<<" ";
// cout<<endl;
for(int i=1;i<=n;++i)
{
cout<<ans[i]+T1.sum(mp[pos[i]])<<endl;
}
}
int main()
{
ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
solve();
return 0;
}