NOI 2007 题解

来源:互联网 发布:2017年网络宣传周主题 编辑:程序博客网 时间:2024/06/07 05:11

社交网络

(传送门)

题意

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

分析

由于点比较少,所以可以用复杂度为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|1const 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;}

 

0 0