• 洛谷 P3588 【[POI2015]PUS】题解


    算法分析:

    首先,有一个最简单的思路,把这个区间里的其它数向这 k" role="presentation" style="position: relative;">k 个数连一条边权为 1" role="presentation" style="position: relative;">1 的边,表示这 k" role="presentation" style="position: relative;">k 个数要比其它的数大,然后跑一边拓扑排序,求最长路。如果有环,那么问题就无解。否则,有解的图一定是一个有向无环图。

    考虑为什么要求最长路。这里的最长路表示该点的最小取值。如果我们求出来的这个点的 disi" role="presentation" style="position: relative;">disi 大于给定数范围的限制,或者当前点是给定的,但我们求出的最长路大于他给定的值,那么就无解,否则直接输出从 1" role="presentation" style="position: relative;">1 到 n" role="presentation" style="position: relative;">n 的 disi" role="presentation" style="position: relative;">disi​ 即可。

    但这道题的边数有 S1×S2" role="presentation" style="position: relative;">S1×S2 条,S1" role="presentation" style="position: relative;">S1 是给定的 k" role="presentation" style="position: relative;">k 个点,S2" role="presentation" style="position: relative;">S2 是其余的点。显然炸了。

    考虑第一个优化,我们可以建一个虚拟点 node" role="presentation" style="position: relative;">node,把 node" role="presentation" style="position: relative;">node 向 k" role="presentation" style="position: relative;">k 个点连一条边权为 1" role="presentation" style="position: relative;">1 的边,然后把其它的区间向虚拟点 node" role="presentation" style="position: relative;">node 连一条边权为 0" role="presentation" style="position: relative;">0 的边,这样我们就可以表示上述所说的把这个区间里的其它数向这 k" role="presentation" style="position: relative;">k 个数连一条边权为 1" role="presentation" style="position: relative;">1 的边。

    但边的条数我们还是不能接受。

    考虑进一步的优化。我们有 m" role="presentation" style="position: relative;">m 个区间,每一个区间最多有 k+1" role="presentation" style="position: relative;">k+1 段。跟区间有关的连边,我们可以想到线段树优化建图。具体就是,线段树的两个孩子节点分别向父节点连一条边权为 0" role="presentation" style="position: relative;">0 的边,然后区间向虚拟点 node" role="presentation" style="position: relative;">node 连边就可以表示为线段树上的一段区间向虚拟点 node" role="presentation" style="position: relative;">node 连边。这样向每段区间最多有 logn" role="presentation" style="position: relative;">logn 条边,区间的总数是 k" role="presentation" style="position: relative;">k,线段树内部有 2n" role="presentation" style="position: relative;">2n 条边,给定的 k" role="presentation" style="position: relative;">k 个点需要连边,这样边的总数是 k+klogn+2n" role="presentation" style="position: relative;">k+klogn+2n,可以过这道题。

    最后说一下如何判环,我们发现在拓扑排序中,把每一个点 i" role="presentation" style="position: relative;">i 如果访问到就 visi=1" role="presentation" style="position: relative;">visi=1 最后看是否有点没有被访问过,如果有的话那就是有环,没有的话直接判最长路即可。

    总代码:

    1. #include
    2. #include
    3. #include
    4. #include
    5. #include
    6. #include
    7. #define re register
    8. #define rep(a,b,c) for(re int a(b) ; a<=c ; ++a)
    9. #define drep(a,b,c) for(re int a(b) ; a>=c ; --a)
    10. using namespace std;
    11. inline int read(){
    12. int x=0,f=1;char ch=getchar();
    13. while(ch<'0'||ch>'9'){if(ch == '-') f=-1 ; ch=getchar();}
    14. while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48) ; ch=getchar();}
    15. return x*f;
    16. }
    17. const int M = 5e5+10;
    18. const int limit = 1e9;
    19. int a[M],dis[M],head[M],vis[M];
    20. int du[M],t[M];
    21. int n,s,m;
    22. int cnt,tot;
    23. struct edge{
    24. int to,nxt,w;
    25. }e[M*10]; //注意不要开小
    26. inline void add(int u,int v,int w){ //加边
    27. e[++cnt].to = v;
    28. e[cnt].w = w;
    29. e[cnt].nxt = head[u];
    30. head[u] = cnt;
    31. du[v]++;
    32. }
    33. inline void build(int k,int l,int r){ //正常的线段树建立
    34. if(l == r){
    35. t[k] = l; //叶节点的编号
    36. return;
    37. }
    38. t[k] = ++tot; //每一个节点的编号
    39. int mid = (l+r)>>1;
    40. build(k<<1,l,mid);
    41. build(k<<1|1,mid+1,r);
    42. add(t[k<<1],t[k],0),add(t[k<<1|1],t[k],0); //左右儿子分别向父亲连边
    43. }
    44. inline void link(int k,int l,int r,int x,int y,int node){
    45. if(x<=l && r<=y){
    46. add(t[k],node,0); // 这个区间向虚拟点node连边
    47. return;
    48. }
    49. int mid = (l+r)>>1;
    50. if(x<=mid) link(k<<1,l,mid,x,y,node);
    51. if(y>mid) link(k<<1|1,mid+1,r,x,y,node); //正常操作
    52. }
    53. inline void topo(){
    54. queue<int> q;
    55. for(re int i(1) ; i<=tot ; ++i){
    56. if(!dis[i]) dis[i] = 1; //从1开始
    57. if(du[i] == 0) q.push(i); //把入度为0的点先加进队列
    58. }
    59. while(!q.empty()){
    60. int u = q.front();
    61. q.pop();
    62. vis[u] = 1; //标记这个点走到了
    63. for(re int i(head[u]) ; i ; i=e[i].nxt){
    64. int v = e[i].to,w = e[i].w;
    65. if(dis[v] < dis[u] + w) dis[v] = dis[u] + w; //更新最长路
    66. if(a[v] && dis[v] > a[v]) printf("NIE\n"),exit(0); //如果不满足条件
    67. du[v]--;
    68. if(!du[v]) q.push(v);
    69. }
    70. }
    71. }
    72. signed main(){
    73. n=read(),s=read(),m=read();
    74. tot = n;
    75. build(1,1,n);
    76. rep(i,1,s){
    77. int pos=read(),x=read();
    78. a[pos] = dis[pos] = x;
    79. }
    80. rep(i,1,m){
    81. int l=read(),r=read(),k=read();
    82. tot++; //tot就是虚拟点
    83. int pre = l-1; //上一段区间的左端点
    84. int x;
    85. for(re int j(1) ; j<=k ; ++j){
    86. x = read();
    87. add(tot,x,1); //虚拟点向x连边
    88. if(x > pre+1) link(1,1,n,pre+1,x-1,tot); //这个区间没有被连边,就连边
    89. pre = x; //更新pre
    90. }
    91. if(x < r) link(1,1,n,x+1,r,tot); //最后看是否会剩一段区间没有连边
    92. }
    93. topo();
    94. for(re int i(1) ; i<=tot ; ++i) if(!vis[i] || dis[i] > limit) { printf("NIE\n"); return 0; } //判无解
    95. printf("TAK\n");
    96. rep(i,1,n) printf("%d ",dis[i]);
    97. return 0;
    98. }

     

  • 相关阅读:
    什么是软件需求?以及需求的最佳实践?
    java毕业设计城镇保障性住房管理系统mybatis+源码+调试部署+系统+数据库+lw
    面试题 02.07. 链表相交
    【python】(十五)python内置库——正则表达式re
    Python的基础语法(十)(持续更新)
    智慧能源发展方向、应用趋势
    计算机组成原理历年考研真题对应知识点(计算机的性能指标)
    mount /dev/mapper/centos-root on sysroot failed处理
    闭眼推荐,9 个不能错过的机器学习数据集
    基于ssm的美妆产品进销存管理系统的设计与开发-计算机毕业设计源码
  • 原文地址:https://blog.csdn.net/glorious_dream/article/details/126330373