正賽的時候完全沒看這個題,事后winterzz告訴我他想出來的解法。
首先題意是給出n個點,m次操作。
操作有一種是連接兩個點,另一種是求此時再為這個圖連k條邊,最少和最多能有幾個聯通塊。
最少的求法很簡單,顯然一條邊可以減少一個聯通塊。
最多的求法則稍微復雜:
首先我們先將所有聯通塊填成完全圖,這部分邊是白給的。
接下來最優的連接方式顯然是將最大的和次大的聯通塊合並,如果還有邊需要連就再將其把第三大的聯通塊合並...一直這樣下去。
這個東西我們顯然可以二分,二分出將多少個聯通塊合並成一起能用完k個邊。
winterzz表示可以splay搞搞,只要支持插入一個點和求后綴和就可以了。
但是仔細想想,其實我們用線段樹也可以做到這個二分。
我們做一個權值線段樹來維護大小為k的塊的個數,和它們的可容納邊總和,與它們的點個數總和,然后在這個線段樹上二分。
但是這樣我們二分到子葉節點k就不知道要合並掉多少個大小為k的塊了,所以我們在子葉節點再做一次二分即可,這樣復雜度仍然只有一個log,因為每次查詢我們只會到一個子葉節點。
以下附上代碼:
#include<bits/stdc++.h>
using namespace std;
#define mid (l+r)/2
int i,i0,n,m,T,pre[100005],siz[100005];
long long sum,num;
int fin(int x){return (pre[x]==x)?x:pre[x]=fin(pre[x]);}
void uni(int x,int y){if(fin(x)!=fin(y))pre[fin(y)]=fin(x);}
struct node
{
long long siz,siz2,siz3;
}tree[400005];
node operator+(node a,node b){return {a.siz+b.siz,a.siz2+b.siz2,a.siz3+b.siz3};}
void b_tree(int l,int r,int p)
{
if(l==r&&l==1)tree[p].siz=tree[p].siz3=n,tree[p].siz2=0;
else tree[p].siz=tree[p].siz2=tree[p].siz3=0;
if(l!=r)b_tree(l,mid,p*2),b_tree(mid+1,r,p*2+1);
}
void add_tree(int l,int r,int p,int a)
{
if(l==r)tree[p].siz+=l,tree[p].siz2+=l*(l-1)/2,tree[p].siz3++;
else
{
if(a<=mid) add_tree(l,mid,p*2,a);
else if(a>=mid+1)add_tree(mid+1,r,p*2+1,a);
tree[p]=tree[p*2]+tree[p*2+1];
}
}
void erase_tree(int l,int r,int p,int a)
{
if(l==r)tree[p].siz-=l,tree[p].siz2-=l*(l-1)/2,tree[p].siz3--;
else
{
if(a<=mid) erase_tree(l,mid,p*2,a);
else if(a>=mid+1)erase_tree(mid+1,r,p*2+1,a);
tree[p]=tree[p*2]+tree[p*2+1];
}
}
int q_tree(int l,int r,int p,long long k,long long v)
{
if(l==r)
{
int ll=1,rr=tree[p].siz3;
while(ll<rr)
{
int mmid=(ll+rr)/2;
if((v+mmid*l)*(v+mmid*l-1)/2>=l*(l-1)/2*mmid+k)rr=mmid;
else ll=mmid+1;
}
return ll;
}
else
{
if((v+tree[p*2+1].siz)*(v+tree[p*2+1].siz-1)/2>=k+tree[p*2+1].siz2)return q_tree(mid+1,r,p*2+1,k,v);
else return q_tree(l,mid,p*2,k+tree[p*2+1].siz2,v+tree[p*2+1].siz)+tree[p*2+1].siz3;
}
}
int main()
{
scanf("%d",&T);
while(T--)
{
scanf("%d %d",&n,&m);
for(i=1;i<=n;i++)pre[i]=i,siz[i]=1;
sum=0,num=n;
b_tree(1,n,1);
for(i=1;i<=m;i++)
{
int op;
scanf("%d",&op);
if(op==1)
{
int x,y;
scanf("%d %d",&x,&y);
if(fin(x)!=fin(y))
{
erase_tree(1,n,1,siz[fin(x)]),erase_tree(1,n,1,siz[fin(y)]);
sum-=siz[fin(x)]*(siz[fin(x)]-1)/2,sum-=siz[fin(y)]*(siz[fin(y)]-1)/2;
siz[fin(x)]=siz[fin(x)]+siz[fin(y)],sum+=siz[fin(x)]*(siz[fin(x)]-1)/2;
add_tree(1,n,1,siz[fin(x)]),uni(x,y);
num--;
}
sum--;
}
else
{
long long k;
scanf("%lld",&k);
printf("%lld %lld\n",max(1ll,(num-k)),num+1-q_tree(1,n,1,k-sum,0));
}
}
}
return 0;
}
