NOI 2007 题解

社交网络

(传送门)

题意

一个加权无向图,每两个点的关系密切度用两点间最短路长度来衡量。因此,每个点的重要程度是经过这个点的最短路径的条数,求每个点的关系密切程度。

分析

由于点比较少,所以可以用复杂度为O(n^3)Floyd来计算两点之间的最短路。

path[i][j]为从ij的最短路径的条数,Floyd

松弛时,更新path[i][j]=0,遇到dist[i][k]+dist[k][j]=dist[i][j]的情况,令path[i][j]+=path[i][k]*path[k][j],这一点比较好想。接下来根据定义求每个点的重要程度,C(s,t)=path[s][t],根据乘法原理,过v的最短路径条数C(s,t,v)=path[s][v]*path[v][t]

注意,最短路径条数什么的需要用double来存避免爆掉。

代码

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

const int maxn=100+1;
int n,m;
double mp[maxn][maxn];
double a[maxn][maxn];
double ans[maxn];

int main()
{
    cin>>n>>m;
    for(int i=1;i<=n;i++)
        for(int j=1;j<=n;j++)
            mp[i][j]=1e15;
    for(int i=1;i<=m;i++)
    {
        int x,y;
        double z;
        scanf("%d%d%lf",&x,&y,&z);
        mp[x][y]=mp[y][x]=z;
        a[x][y]=a[y][x]=1;
    }
    for(int k=1;k<=n;k++)
        for(int i=1;i<=n;i++)
            for(int j=1;j<=n;j++)
            {
                if(mp[i][k]+mp[k][j]<mp[i][j])
                {
                    mp[i][j]=mp[i][k]+mp[k][j];
                    a[i][j]=0;
                }
                if(mp[i][k]+mp[k][j]==mp[i][j])
                  a[i][j]+=a[i][k]*a[k][j];
            }

    for(int i=1;i<=n;i++)
        a[i][i]=0;
    
    for(int k=1;k<=n;k++)
        for(int i=1;i<=n;i++)
            for(int j=1;j<=n;j++)
                if(mp[i][k]+mp[k][j]==mp[i][j]&&a[i][j]>0)
                    ans[k]+=a[i][k]*a[k][j]/a[i][j];

    for(int i=1;i<=n;i++)
      printf("%.3lf\n",ans[i]);
    return 0;
}

货币兑换

(传送门)

题意

经过你对于金钱的运作,求最后最多能获得多少钱。

分析

DP+CDQ分治

要想最大获利一定是在某一个适当的时候全部买入或者全部卖出,当然这样的理想的情况,现实中一定不会有,题目说的买卖一部分是瞎扯淡的。朴素的dp方程就很好想了:f[i]=(rate[j]*f[j]*a[i]+f[j]*b[i])/(rate[i]*a[i]+b[i]),整理,设x[j]=rate[j]*f[j]/( rate[i]*a[i]+b[i]),y[j]=f[j]/(rate[i]*a[i]+b[i]),那么就有f[i]=a[i]*x[i]+b[i]*y[i]。这样可以用斜率优化,但xy都不单调,不能用单调队列维护,需要维护一个凸壳,splay维护实在太麻烦,所以学习CDQ分治吧。

某大犇把CDQ分治大概为这几步:

1. 递归到底直接处理答案

2. 取mid,递归解决[l,mid]

3. 处理[l,mid][mid+1,r]的影响

4. 递归解决[mid+1,r]

5. 归排,给处理下一个区间的影响做准备。

代码

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

const int maxn=100005;
const double eps=1e-9;

int n;
double dp[maxn];
struct point
{
  	double x,y;
  	double a,b,k,rate;
  	int id;
} p[maxn],t[maxn];
bool cmp(point aa,point bb)
{
  	return aa.k>bb.k;
}

double slope(int a,int b)
{
  	if(!b)return -1e20;
  	if(fabs(p[a].x-p[b].x)<eps)return 1e20;
  	return (p[b].y-p[a].y)/(p[b].x-p[a].x);
}

void solve(int l,int r)
{
  	if(l==r)
  	{//分治到底直接计算出结果
    	dp[l]=max(dp[l],dp[l-1]);
    	p[l].y=dp[l]/(p[l].a*p[l].rate+p[l].b);
    	p[l].x=p[l].rate*p[l].y;
    	return;
  	}
  	int l1,l2,mid=(l+r)>>1;
  	l1=l,l2=mid+1;
  	for(int i=l;i<=r;i++)
  	{
      	if(p[i].id<=mid)t[l1++]=p[i];
      	else t[l2++]=p[i];
  	}
  	for(int i=l;i<=r;i++)p[i]=t[i];

  	//将一块原顺序分为左右两块 
  	solve(l,mid);//递归左边
  	int top=0,stack[maxn];
  	for(int i=l;i<=mid;i++)
  	{//左边维护一个凸包
      	while(top>1 && slope(stack[top-1],stack[top])<slope(stack[top-1],i)+eps) top--;
   			stack[++top]=i;
  	}
    stack[++top]=0;
    int now=1;
    for(int i=mid+1;i<=r;i++)
    {//用左边的点作为决策更新右边 
      	while(now<top && slope(stack[now],stack[now+1])+eps>p[i].k) now++;
        	dp[p[i].id]=max(dp[p[i].id],p[stack[now]].x*p[i].a+p[stack[now]].y*p[i].b);
  	}
  	solve(mid+1,r);//递归右边 
  	l1=l,l2=mid+1;
  	for(int i=l;i<=r;i++)
  	{
      	if(((p[l1].x<p[l2].x ||(fabs(p[l1].x-p[l2].x)<eps && p[l1].y<p[l2].y)) || l2>r) && l1<=mid)
       	 	t[i]=p[l1++];
      	else
        	t[i]=p[l2++];
  	}
  	for(int i=l;i<=r;i++)p[i]=t[i];
}

int main()
{
  scanf("%d%lf",&n,&dp[0]);
  for(int i=1;i<=n;i++)
  {
    scanf("%lf%lf%lf",&p[i].a,&p[i].b,&p[i].rate);
    p[i].k=-p[i].a/p[i].b;
    p[i].id=i;
  }
  sort(p+1,p+n+1,cmp);//按斜率进行排序,保证分治的每一块斜率是有序的 
  solve(1,n);
  printf("%.3lf",dp[n]);
  return 0;
}

项链工厂

(传送门)

题意

维护一个环,支持6种操作:

1. 顺时针旋转k

2. 沿点1所在直径翻转

3. 两个珠子互换

4. 一段区间染色

5. 查询环上有多少个颜色段

6. 查询一段区间有多少颜色段

分析

要是少了两种操作就是赤裸裸的线段树,加上的话可以用splay做,当然,线段树上加一些东西也是可做的。只要把珠子的移动看成珠子标号的移动就可以了。对于R k的操作,把位置1的标号后移k位,对于F操作记录一个方向,每次取反就能搞定了,判断有些麻烦但是比splay好写多了。

代码

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

#define lson t<<1
#define rson t<<1|1
const int MAXN=500000+5;

struct treenode
{
    int l,r,l_col,r_col,part;
    bool mark;
} tree[4*MAXN];
int color[MAXN];
int delta,n,m,cur_col;
bool rev;

void pushup(int t)
{
    tree[t].l_col=tree[lson].l_col;
    tree[t].r_col=tree[rson].r_col;
    tree[t].part=tree[lson].part+tree[rson].part-(tree[lson].r_col==tree[rson].l_col);
}

void build(int t)
{
    if(tree[t].l==tree[t].r)
    {
        tree[t].l_col=tree[t].r_col=color[tree[t].l];
        tree[t].part=1;
        return ;
    }
    int mid=(tree[t].l+tree[t].r)>>1;
    tree[lson].l=tree[t].l; tree[lson].r=mid;
    tree[rson].l=mid+1; tree[rson].r=tree[t].r;
    build(lson); build(rson);
    pushup(t);
}

void make(int &x,int &y)
{
    if(rev==0)
    {
        x=(x-delta+n)%n;
        y=(y-delta+n)%n;
    }
    else
    {
        x=(2*n+2-delta-x)%n;
        y=(2*n+2-delta-y)%n;
        swap(x,y);
    }
    if(x==0) x=n;
    if(y==0) y=n;
}

void pushdown(int t)
{
    if(!tree[t].mark) return;
    tree[lson].l_col=tree[lson].r_col=tree[rson].l_col=tree[rson].r_col=tree[t].l_col;
    tree[lson].part=tree[rson].part=1;
    tree[lson].mark=tree[rson].mark=1;
    tree[t].mark=0;
}

void uptate(int t,int x,int y,int c)
{
    if(tree[t].l==x&&tree[t].r==y)
    {
        tree[t].l_col=tree[t].r_col=c;
        tree[t].part=1;
        tree[t].mark=1;
        return ;
    }
    pushdown(t);
    int mid=(tree[t].l+tree[t].r)>>1;
    if(y<=mid) uptate(lson,x,y,c);
    else if(x>mid) uptate(rson,x,y,c);
    else uptate(lson,x,mid,c),uptate(rson,mid+1,y,c);
    pushup(t);
}

int query(int t,int x,int y)
{
    if(tree[t].l==x&&tree[t].r==y)
    {
        cur_col=tree[t].l_col;
        return tree[t].part;
    }
        pushdown(t);
    int mid=(tree[t].l+tree[t].r)>>1;
    if(y<=mid) return query(lson,x,y);
    else if(x>mid) return query(rson,x,y);
    else return query(lson,x,mid)+query(rson,mid+1,y)-(tree[lson].r_col==tree[rson].l_col);
}

int main()
{
    int x,y,z,temp1,temp2;
    char c[10];
    rev=delta=0;

    cin>>n>>m;
    for(int i=1;i<=n;i++)
        scanf("%d",&color[i]);
    tree[1].l=1; tree[1].r=n;
    build(1);
    int T;
    cin>>T;
    while(T--)
    {
        scanf("%s",c);
        if(c[0]=='R')
        {
            scanf("%d",&x);
            if(rev==0)
                delta=(delta+x)%n;
            else
                delta=(delta-x+n)%n;
        }
        if(c[0]=='F')
            rev^=1;
        if(c[0]=='S')
        {
            scanf("%d%d",&x,&y);
            make(x,y);
            query(1,x,x); temp1=cur_col;
            query(1,y,y); temp2=cur_col;
            uptate(1,x,x,temp2);
            uptate(1,y,y,temp1);
        }
        if(c[0]=='P')
        {
            scanf("%d%d%d",&x,&y,&z);
            make(x,y);
            if(x<=y)
                uptate(1,x,y,z);
            else
            {
                uptate(1,x,n,z);
                uptate(1,1,y,z);
            }
        }
        if(c[0]=='C'&&c[1]!='S')
        {
            int ans=query(1,1,n);
            query(1,1,1); temp1=cur_col;
            query(1,n,n); temp2=cur_col;
            if(temp1==temp2)
                ans=max(ans-1,1);
            printf("%d\n",ans);
        }
        if(c[0]=='C'&&c[1]=='S')
        {
            scanf("%d%d",&x,&y);
            make(x,y);
            if(x<=y) printf("%d\n",query(1,x,y));
            else
            {
                int ans=query(1,x,n)+query(1,1,y);
                query(1,1,1); temp1=cur_col;
                query(1,n,n); temp2=cur_col;
                if(temp1==temp2)
                    ans=ans-1;
                printf("%d\n",ans);
            }
        }
    }
    return 0;
}


生成树计数

(传送门)

题意

n(n<=10^15)个点,序号1~n,只有序号相差不超过k(k<=5)的点之间才可以连边,问生成树的种数mod 65521。利用矩阵计算生成树个数

分析

按照题目的提示,对于有n个点的图,构造一个矩阵,满足:若i==ja[i][j]=du[i](du[i]为节点i的度数)。否则,若点i和点j有边,a[i][j]=-1。若无边,a[i][j]=0

然后求这个矩阵任何一个n-1阶主子式的行列式的绝对值,得到的即是这个图的生成树个数。如果只是这样做只有40分,但是求行列式用高斯消元优化可以得60分,最小表示法加预处理某个状态s转移到S’有几种可以得到80分,经过预处理可以看出转移可以用一个m*m的矩阵表示,由于矩阵乘法满足结合律,所以可以用矩阵快速幂来求出,经过这样一步步优化,可以得到满分。

代码

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

const long long MOD=65521;
const int MAXK=5,MAXS=60;
const int val[6]= {1,1,1,3,16,125};
int status[MAXS],hash[1<<(3*MAXK)];
int p[MAXK+1],cot[MAXK];
long long n;
int k,tot;

struct Matrix
{
	int h,w;
	long long mx[MAXS][MAXS];

	Matrix()
	{
		h=w=0;
		memset(mx,0,sizeof(mx));
	}
	Matrix operator * (const Matrix &b) const
	{
		Matrix tmp;
        memset(tmp.mx,0,sizeof(tmp.mx));
		tmp.h=h;
        tmp.w=b.w;
        for(int i=0;i<h;i++)
            for(int j=0;j<b.w;j++)
                for(int k=0;k<w;k++)
                    tmp.mx[i][j]=(tmp.mx[i][j]+(mx[i][k]*b.mx[k][j])%MOD)%MOD;
        return tmp;
    }

    void initE()
    {
    	memset(mx,0,sizeof(mx));
        for(int i=0;i<w;i++)
            mx[i][i]=1LL;
    }

    Matrix mpow(long long  k)
    {
        Matrix c,b;
        c=(*this);
        memset(b.mx,0,sizeof(b.mx));
        b.w=w;
        b.h=h;
        b.initE();
        while(k)
        {
            if(k&1LL)
            {
                b=b*c;
            }
            c=c*c;
            k>>=1LL;
        }
        return b;
    }
} g,f;

void dfs(int mask,int deep)
{
	if(deep==k)
	{
		g.mx[0][tot]=1;
		memset(cot,0,sizeof(cot));
		for(int i=0;i<k;i++)
			cot[mask>>(i*3)&7]++;
        for(int i=0; i<k; i++)
            g.mx[0][tot]*=val[cot[i]];
        status[tot]=mask;
        hash[mask]=tot++;
        return;
	}
	int tmp=-1;
	for(int i=0;i<deep;i++)
        tmp=max(tmp,mask>>(i*3)&7);
    for(int i=0;i<=tmp+1&&i<k;i++)
        dfs(mask<<3|i,deep+1);
}

int findp(int x)
{
    return p[x]==-1?x:p[x]=findp(p[x]);
}

int justify()
{
	bool vis[MAXK];
	memset(vis,0,sizeof(vis));
	int tot=0,mask=0;
	for(int i=k-1;i>=0;i--)
		if(!vis[i])
		{
			vis[i]=1;
			mask|=tot<<(i*3);
            for(int j=i-1;j>=0;j--)
                if(findp(i+1)==findp(j+1))
                {
                    vis[j]=1;
                    mask|=tot<<(j*3);
                }
            tot++;
		}
	return hash[mask];
}

void cal(int s,int mask)
{
	memset(p,-1,sizeof(p));
	for(int i=0;i<k;i++)
        for(int j=i+1;j<k;j++)
            if((status[s]>>(i*3)&7)==(status[s]>>(j*3)&7))
            {
                int px=findp(i),py=findp(j);
                if(px!=py) p[px]=py;
            }
    for(int i=0;i<k;i++)
        if((mask>>i)&1)
        {
            int px=findp(i),py=findp(k);
            if(px==py) return;
            p[px]=py;
        }
    bool flag=0;
    for(int i=1;i<=k;i++)
    	if(findp(i)==findp(0))
    	{
    		flag=1;
    		break;
    	}
    if(!flag) return;
    f.mx[s][justify()]++;
}

int main()
{
	while(cin>>k>>n)
	{
		memset(f.mx,0,sizeof(f.mx));
        memset(g.mx,0,sizeof(g.mx));
        tot=0;
        dfs(0,0);
        g.h=1;
        g.w=f.w=f.h=tot;
        for(int i=0;i<tot;i++)
            for(int mask=0;mask<(1<<k);mask++)
                cal(i,mask);

        g=g*f.mpow(n-k);
        printf("%lld\n",g.mx[0][0]);
	}
	return 0;
}

追捕盗贼

(传送门)

题意

对于题目描述的几种方法,求出一个追捕盗贼的需要警察数最少的方案。

分析

f[i]表示以i为根的子树的最小需求量,对于一颗子树的根,有两种情况,派与不派。如果只有一颗子树需求量最大,那f[i]=max{f[son[i]]},因为在子树父亲被驻守时,总可以先驻守子树的根,派一部分人解决其余子树,回来后所有警察一起解决最长链,如果有一颗以上子树需求量同时达到最大,f[i]=max{f[son[i]]+1},因为必须一直派一个警察驻守当前的根,其余的一个一个解决子树。最终f[root]为答案,同时,有些时候可以先处理儿子再处理根,也可以枚举root,然后取最小,但是这样无法处理总根的子树的类似的情况。这样的算法是看某大犇题解学到的,据说只能拿到92~96分,毕竟上述特殊情况几乎没有。正解是用了很多数学知识,很难懂,这样简单算法的分数算比较高的了,在一部分OJ上还可以AC

代码

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

const int INF=0x3f3f3f3f;
const int MAXN=1000+5;

struct node
{
  char order;
  int x,y;
} way[MAXN*20];
int cnt,n,ans=INF,asid;
int f[MAXN];
vector<int> edges[MAXN];

void Land(int x){ way[++cnt].order='L'; way[cnt].x=x; }
void Back(int x){ way[++cnt].order='B'; way[cnt].x=x; }
void Move(int x,int y){ way[++cnt]=(node){'M',x,y}; }

void dfs(int x,int fa)
{
  	int maxx=0,msize=0,son=0;
  	for(int i=0;i<edges[x].size();i++)
  	{
  		int v=edges[x][i];
    	if(v!=fa)
    	{
      		dfs(v,x);
      		if(f[v]==maxx) msize++;
      		if(f[v]>maxx)
      		{
      			maxx=f[v];
      			msize=1;
      		}
      		son++;
    	}
  	}
  	if(msize>1) f[x]=maxx+1;
  	else f[x]=maxx;
  	if(son==0) f[x]++;
}

void solve(int x,int fa,int t,int d)
{
  	int maxx=0,msize=0,son=0,mnum=-1;
  	for(int i=0;i<edges[x].size();i++)
  	{
  		int v=edges[x][i];
    	if(v!=fa)
    	{
      		solve(v,x,0,0);
      		if(f[v]==maxx) msize++;
      		if(f[v]>maxx)
      		{
      			maxx=f[v];
      			msize=1;
      			mnum=v;
      		}
      		son++;
    	}
  	}
  	if(d==1)
  	{
    	for(int i=0;i<edges[i].size();i++)
      	{
      		int v=edges[x][i];
      		if(v!=fa && v!=mnum)
        		{
          			Land(x);
          			Move(x,v);
          			solve(v,x,1,1);
        		}
      	}
    	if(son==0)
    	{
    		Back(x);
    		return;
    	}
    	Move(x,mnum);
    	solve(mnum,x,1,1);
  	}
  	if(msize>1) f[x]=maxx+1;
  	else f[x]=maxx;
  	if(son==0) f[x]++;
}

int main()
{
	cin>>n;
	for(int i=1;i<n;i++)
	{
		int a,b;
		scanf("%d%d",&a,&b);
		edges[a].push_back(b);
		edges[b].push_back(a);
	}

	for(int i=1;i<=n;i++)
  	{
    	memset(f,0,sizeof(f));
    	dfs(i,0);
    	if(f[i]<ans)
    	{
    		ans=f[i];
    		asid=i;
  		}
  	}
  	cout<<ans<<endl;
  	
  	memset(f,0,sizeof(f));
 	Land(asid);
 	solve(asid,0,1,1);
  	cout<<cnt<<endl;

  	for(int i=1;i<=cnt;i++)
  	{
    	if(way[i].order=='M')
    		printf("%c %d %d\n",way[i].order,way[i].x,way[i].y);
    	else printf("%c %d\n",way[i].order,way[i].x);
  	}
	return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值