您的位置:首页 > 其它

NOI 2007 题解

2015-06-01 08:49 225 查看

社交网络

(传送门)

题意

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

分析

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

path[i][j]为从i到j的最短路径的条数,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]。这样可以用斜率优化,但x,y都不单调,不能用单调队列维护,需要维护一个凸壳,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
);
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==j,a[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;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: