zoukankan      html  css  js  c++  java
  • 七夕祭

    七夕祭

    给出一个(n imes m)的网格图,以及t个棋子的位置,每次操作可以选择将一个棋子向上下左右移动,注意第一行与第n行相邻,第一列与第n列相邻,如棋子可以从第一行移动到第n行,现在有2个条件

    1. 保证每一行的棋子数相同
    2. 保证每一列的棋子数相同

    询问尽量满足上诉要求的最少移动次数,(1≤n,m≤100000)

    注意到一个性质,当棋子上下移动的时候,只会改变每一行拥有的棋子数,而棋子左右移动只会改变每一列拥有的棋子数,换个意思就是棋子的上下移动和左右移动可以作为两个问题看待,也就是要求可以分别处理,这样问题就大大简化了,接下来设棋子数为(tot)

    于是设(h[i])表示第i行拥有的棋子数,(l[i])为第i列拥有的棋子数,不妨拿行出来研究,现在的问题转化成,数列({h[i]})的中间一个位置可将自己的部分数字交给它相邻的位置,而这个数字就是操作次数,每一行要平均分到(tot/n),如果(tot\% n)不为0显然不能均分,显然这个问题类似均分纸牌,如果设({H_i})({h_i})的前缀和,那么对于数列前i个数来看,它拥有的数有(H_i),但是它只需要(tot/n imes i),于是第i+1个数要交给或接受这一个整体(|H_i-tot/n imes i|),于是容易知道当不存在第一行与最后一行相邻的时候,我们有答案(sum_{i=1}|H_i-tot/n imes i|)

    但是注意相邻,类似一个环,于是我们可以考虑处理环的基本办法,这道题采取了二次递推,名不符实,因为二次递推是递推里的做法,但基本思想都是拆环以后,根据一个拆环的维护的数据来推出其他拆环的数据(犹如二次扫描+换根法)

    [h_{k+1},h_{k+2},...,h_n,h_1,...,h_k ]

    此时按照均分纸牌的思路容易知道,答案应该为

    [|h_{k+1}-n/tot|+|h_{k+1}+h_{k+2}-n/tot imes 2|+...+|h_{k+1}+...+h_{n}-n/tot imes (n-k)|+ ]

    [|h_{k+1}+...+h_n+h_1+n/tot imes (n-k+1)|+...+|h_{k+1}+...h_n+h_1+...+h_k-n/tot imes n| ]

    很难想到设(f_i=H_i-tot/n imes i)的前缀和,于是有

    [|f_{k+1}-f_k|+|f_{k+2}-f_k|+...+|f_n-f_k|+|f_n-f_k+f_1|+...+|f_n-f_k+f_k| ]

    因为(f_i)的绝对值表示前i个位置需要第i+1个位置所或者给的数字,于是(f_n=0),因为前n个位置不要得到数字,否则无解(从代数上来看(f_n=H_n-tot/n imes n=H_n-tot=tot-tot=0)),所以有

    [|f_{k+1}-f_k|+|f_{k+2}-f_k|+...+|f_n-f_k|+|f_1-f_k|+...+|f_k-f_k|= ]

    [sum_{i=1}^n|f_i-f_k| ]

    现在问题就转化成了求出哪个(f_k)可以使这个式子最小化,显然这就是列车调度,取中位数即可,时间复杂度可以做到(O((n+m)log(n)))

    我已经很久不小结题目了,但是此题实在是让我认识到({large ext{我太菜了}})

    先总结一下网格图的基本做法

    首先它的考虑点应该是从行列,对角线,矩形,轮廓来考虑的(细节是(1 imes n,n imes 1)的网格图,你该输出什么)

    而一些比较骚的做法是拆行成列(八数码问题,高斯消元)和行列独立(看看行列之间的关系是否独立,例子比如此题还有一大堆错排问题)

    然后就是环的问题处理,首要拆环成链,然后考虑是要再补一截,记录一截还是二次递推

    而接下来的代数变换就骚的教我做人,根本想不到设哪个为一个整体,然后就推,发现原来是一个列车调度问题。

    于是根据此题,我们得出一个著名的结论,({large ext{我太菜了}})

    参考代码:

    #include <iostream>
    #include <cstdio>
    #include <algorithm>
    #define il inline
    #define ri register
    #define ll long long
    using namespace std;
    ll ans;
    int g[100001],k[100001],tot,check;
    il void read(int&),work(int,int[]);
    template<class free>
    il free Abs(free);
    int main(){int n,m,t;
    	read(n),read(m),read(t);
    	for(int i(1),y,x;i<=t;++i)
    		read(y),read(x),++g[y],++k[x],++tot;
    	if(!(tot%n))check|=1,work(n,g);if(!(tot%m))check|=2,work(m,k);
    	switch(check){
    	case 0:cout<<"impossible ";break;
    	case 1:cout<<"row ";break;
    	case 2:cout<<"column ";break;
    	case 3:cout<<"both ";break;
    	}if(check)printf("%lld",ans);
    	return 0;
    }
    template<class free>
    il free Abs(free x){
    	return x<0?-x:x;
    }
    il void work(int n,int a[]){
    	for(int i(1);i<=n;++i)
    		a[i]-=tot/n,a[i]+=a[i-1];
    	sort(a+1,a+n+1);int mid(a[1+n>>1]);
    	for(int i(1);i<=n;++i)
    		ans+=Abs(mid-a[i]);
    }
    il void read(int &x){
    	x^=x;ri char c;while(c=getchar(),c<'0'||c>'9');
    	while(c>='0'&&c<='9')x=(x<<1)+(x<<3)+(c^48),c=getchar();
    }
    
    
  • 相关阅读:
    JAVA_WEB--jsp概述
    npr_news英语新闻听力——每日更新
    词根词缀高效背单词技巧--词霸天下完整版
    python刷LeetCode:1071. 字符串的最大公因子
    python刷LeetCode:1013. 将数组分成和相等的三个部分
    python刷LeetCode:543. 二叉树的直径
    python刷LeetCode:121. 买卖股票的最佳时机
    python刷LeetCode:38. 外观数列
    python刷LeetCode:35. 搜索插入位置
    python刷LeetCode:28. 实现 strStr()
  • 原文地址:https://www.cnblogs.com/a1b3c7d9/p/11213980.html
Copyright © 2011-2022 走看看