1. 程式人生 > >2017多校訓練賽第一場 HDU 6039 Gear Up(線段樹+並查集)

2017多校訓練賽第一場 HDU 6039 Gear Up(線段樹+並查集)

Gear Up

Time Limit: 8000/4000 MS (Java/Others)    Memory Limit: 131072/131072 K (Java/Others)
Total Submission(s): 59    Accepted Submission(s): 16


 

Problem Description

constroy has some gears, each with a radius. Two gears are considered adjacent if they meet one of the following conditions:

1. They share a common edge (i.e. they have equal linear velocity).

2. They share a common shaft (i.e. they have equal angular velocity).

It is guaranteed that no pair of gears meets both of the above conditions.

A series of continuous adjacent gears constitutes a gear path. There is at most one gear path between each two gears.

Now constroy

assigns an angular velocity to one of these gears and then asks you to determine the largest angular velocity among them.

sd0061 thinks this problem is too easy, so he replaces some gears and then asks you the question again.

Input

There are multiple test cases (about 30).

For each test case:

The first line contains three integers n

,m,q, the number of gears, the number of adjacent pairs and the number of operations. (0≤m<n≤105,0≤q≤105)

The second line contains n integers, of which the i-th integer represents ri, the radius of the i-th gear. (ri∈{2λ∣0≤λ≤30})

Each of the next m lines contains three integers a,x,y, the x-th gear and the y
-th gear are adjacent in the a-th condition. (a∈{1,2},1≤x,yn,xy)

Each of the next q line contains three integers a,x,y, an operation ruled in the following: (a∈{1,2},1≤xn,y∈{2λ∣0≤λ≤30})

a=1 means to replace the x-th gear with another one of radius y.

a=2 means to assign angular velocity y to the x-th gear and then determine the maximum angular velocity.

Output

For each test case, firstly output "Case #x:" in one line (without quotes), where x indicates the case number starting from 1, and then for each operation of a=2, output in one line a real number, the natural logarithm of the maximum angular velocity, with the precision of3 digits.

Sample Input

4 3 4 1 4 16 2 1 2 4 1 2 3 2 1 4 1 1 16 1 2 4 2 4 4 1 4 16 4 3 5 2 16 4 8 2 1 2 1 2 3 1 1 4 2 1 4 1 3 8 2 1 16 1 4 1 2 1 8

Sample Output

Case #1: 1.386 Case #2: 2.773 3.466 2.773

Source

        這題其實沒有想象中的那麼難,只是當時沒有時間去想清楚。

        大致題意:給你一些齒輪,有些齒輪是共邊(即線速度相同),有些齒輪是共軸(即角速度相同),每個齒輪都有自己的半徑而且大小都是2的次方,保證不會出現矛盾,然後總共有兩種操作,一是改變某個齒輪的半徑,二是詢問如果給某一個齒輪一個角速度(大小也是2的次方),所有齒輪中角速度最大的角速度取自然對數後是多少。

        根據物理知識,如果兩個齒輪共邊,那麼有w1r1=w2r2,為了方便計算,我們不妨取log2(題目資料剛好是2的次方,然後log2是系統自帶函式)。如此一來,我們就可以計算出每一個節點與某一個參考節點的半徑對數之差,到最後我們就可以用logw=logw0+logr0-logr來求結果。然後對於共軸的情況,我們就可一把他們看作一個集合,用並查集縮點維護。

        然後,我們看看修改的操作,首先當然要計算出半徑的該變數delta。接著我們就要分情況討論一下了,如果修改的點與他的父親的線速度不相同,那麼我們需要改變的,只有與這個改變點共線的齒輪,這個容易理解,因為改變半徑的同時並不影響角速度,所以與其共軸的點並沒有被影響。然後那些與改變點共線的點與根的半徑差都要改變delta。另一種情況就比較複雜了,如果修改的點與父親的線速度相同,那麼所有與該點共角速度的點以及他們的後代與根的半徑差都要減少delta。這個如何理解呢?首先,我們看所有與該點共軸的點,我們知道他們的線速度與父親不同,是通過角速度來與父親關聯的,現在改變點半徑改變delta,那麼相應角速度就要改變-delta,體現到共軸點身上就是與父親半徑差改變-delta,相當於與根半徑差該便-delta。連鎖下去就又出現第一種情況,共軸的點半徑差改變,那麼其後代與之共線的點也要變。改變子樹的值的話我們就用線段樹,對樹的dfs序建立線段樹,維護區間更新和區間最大值即可。

        如此一來,我們就完成了比較複雜的修改操作。然後對於詢問操作相對來說簡單一些,給定角速度的log與當前半徑差的差再加上整棵樹中最大的半徑差,結果就是最大的角速度的log,最後乘上一個log(2),就是我們想要的最大角速度的自然對數。具體見程式碼吧:

#include<bits/stdc++.h>
#define N 101000
using namespace std;

int n,m,q,cnt,l[N],r[N],ll[N],rr[N],rt[N];
typedef pair<int,int> P;
typedef pair<P,int> PP;
vector<int> g[N];
vector<PP> gg[N];
int d[N],rad[N];
bool v[N];

struct ST
{
	struct node
	{
		int max,lazy;
		int l,r;
	} tree[N<<2];

	inline void push_up(int i)
	{
		tree[i].max=max(tree[i<<1].max,tree[i<<1|1].max);
	}

	inline void build(int i,int l,int r)
	{
		tree[i].r=r;
		tree[i].l=l;
		tree[i].lazy=0;
		if (l==r)
		{
			tree[i].max=d[l];
			return;
		}
		int mid=(l+r)>>1;
		build(i<<1,l,mid);
		build(i<<1|1,mid+1,r);
		push_up(i);
	}

	inline void push_down(int i)
	{
		tree[i<<1].lazy+=tree[i].lazy;
		tree[i<<1|1].lazy+=tree[i].lazy;
		tree[i<<1].max+=tree[i].lazy;
		tree[i<<1|1].max+=tree[i].lazy;
		tree[i].lazy=0;
	}

	inline void update(int i,int l,int r,int x)
	{
		if ((tree[i].l==l)&&(tree[i].r==r))
		{
			tree[i].lazy+=x;
			tree[i].max+=x;
			return;
		}
		if (tree[i].lazy!=0) push_down(i);
		int mid=(tree[i].l+tree[i].r)>>1;
		if (mid>=r) update(i<<1,l,r,x);
		else if (mid<l) update(i<<1|1,l,r,x);
		else
		{
			update(i<<1,l,mid,x);
			update(i<<1|1,mid+1,r,x);
		}
		push_up(i);
	}

	inline int getmax(int i,int l,int r)
	{
		if ((tree[i].l==l)&&(tree[i].r==r)) return tree[i].max;
		if (tree[i].lazy!=0) push_down(i);
		int mid=(tree[i].l+tree[i].r)>>1;
		if (mid>=r) return getmax(i<<1,l,r);
		else if (mid<l) return getmax(i<<1|1,l,r);
		else return max(getmax(i<<1,l,mid),getmax(i<<1|1,mid+1,r));
	}

} seg;

int f[N];

int find(int x)
{
	return f[x]==x ? x:(f[x]=find(f[x]));
}

void dfs(int root,int fa,int x,int dist)
{
    rt[x]=root;
    l[x]=++cnt;							//l表示某一個角速度相同的集合以及它所有後代的dfs序的左端點
    d[cnt]=dist;
    for(int i=0;i<gg[x].size();i++)
    {
        int w=gg[x][i].second;
        int y=gg[x][i].first.first;
        int u=gg[x][i].first.second;
        if (y!=fa)
        {
            ll[u]=min(ll[u],cnt+1);				//ll表示某一個節點的子樹dfs序的左端點
            dfs(root,x,y,dist+w);
            rr[u]=max(rr[u],cnt);				//rr表示某一個節點的子樹dfs序的右斷點
        } else v[u]=1;						//v表示該點是否與父親共線
    }
    r[x]=cnt;							//r表示某一個角速度相同的集合以及它所有後代的dfs序的左端點
}

int main()
{
    int T_T=0;
    while(~scanf("%d%d%d",&n,&m,&q))
    {
        cnt=0;
        for(int i=1;i<=n;i++)
        {
            ll[i]=0x7fffffff;
            scanf("%d",&rad[i]);
            rad[i]=log2(rad[i]);
            rt[i]=rr[i]=v[i]=0; f[i]=i;
            g[i].clear(); gg[i].clear();
        }
        while(m--)
        {
            int ty,u,v;
            scanf("%d%d%d",&ty,&u,&v);
            if (ty==1)
            {
                g[u].push_back(v);
                g[v].push_back(u);
            } else f[find(u)]=find(v);				//共軸則合併
        }
        for(int i=1;i<=n;i++)
            for(int j=0;j<g[i].size();j++)
            {
                int y=g[i][j];
                gg[find(i)].push_back(PP(P(find(y),i),rad[i]-rad[y]));				//以集合來構建新的樹
            }
        for(int i=1;i<=n;i++)
            if (i==find(i)&&!rt[i]) dfs(i,0,i,0);					//標dfs序,可能是多棵樹,但不影響線段樹本身
        seg.build(1,1,cnt);
        printf("Case #%d:\n",++T_T);
        while(q--)
        {
            int op,x,y;
            scanf("%d%d%d",&op,&x,&y);
            y=log2(y);
            if (op==1)
            {
                int delta=y-rad[x];
                int f=find(x);
                if (v[x]) seg.update(1,l[f],r[f],-delta);			//如果與父親共線,先改變整個集合及其後代	
                if (ll[x]<=rr[x]) seg.update(1,ll[x],rr[x],delta);		//如果不共線,只需改變節點子樹;共線則子樹不用改變,加回去抵消剛剛減少的
                rad[x]=y;
            } else
            {
                x=find(x);
                int ans=y-seg.getmax(1,l[x],l[x])+seg.getmax(1,l[rt[x]],r[rt[x]]);	//結算結果
                printf("%.3f\n",ans*log(2.));					//記得乘ln2
            }
        }
    }
    return 0;
}

覺得博主寫的好的話,打賞一下吧,互利互惠……