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; }
相关文章推荐
- Android下利用Fragment+RadioGroup和TabHost实现底部选项卡的效果
- python自动生成iOS各尺寸规格icon
- 1415-2团队博客汇总表
- jQuery 获取Select选择的Text和 Value
- 【边玩边学Unity3d】实现可编辑网格
- C#的百度地图开发(一)发起HTTP请求
- Asp.Net之后台加载JS和CSS
- Visual Studio使用正则表达式快速统计总共代码行数
- 鸟哥的linux私房菜学习笔记 ---第6章-1
- jQuery 中$.ajax()方法参数详解
- 【边玩边学Unity3d】Mesh属性
- 《Entity Framework 6 Recipes》中文翻译系列 (34) ------ 第六章 继承与建模高级应用之多条件与QueryView
- C# 并行编程 之 PLINQ 基本使用
- .NET平台开源项目速览(2)Compare .NET Objects对象比较组件
- 中软国际java 笔试 面试题
- 关于c++随机数的小问题
- C# 并行编程 之 原子操作
- DOM、JDOM、DOM4J的区别
- 五种设计原则
- jquery.unobtrusive-ajax.js的扩展,做到片段式加载