震惊!!只要刷了这些题,你也学不会ACM!!

CH0201 费解的开关

位运算+贪心:位运算枚举第一行,2-5行可借用前一行结果得出。

CH0202:Hanoi

三柱Hanoi : d[n] = 2d[n-1] +1
四柱Hanoi : f[n] = min(2
f[i]+d[n-1]) 1<=i

POJ1845 sumdiv

求A的B次方所有约数之和mod9901 (A<5*1e7)
对A分解质因数
sum(p,c)=1+p+p^2+…+p ^c
分治求sum 分奇偶:
c为奇:sum(p,c)
= (1+p+…+p^(c-1)/2) + p ^(c+1)/2 * (1+p+…+p ^(c-1)/2)
=(1+p ^(c+1)/2) *sum(p,(c-1)/2)
c为偶:sum(p,c)
=(1+p ^(c/2)) *sum(p,c/2-1)+p ^ c

POJ3889 城市扩建:

分形

BZOJ1218 激光炸弹

二维前缀和

CH0304 IncDec Sequence

经典差分出三种情况
选择 ai 和 aj
选择 ai 和 an+1
选择 a1z 和 aj
产生 |p-q|+1 种不同的b1的值
所以序列可能就是 |p-q|+1

POJ3263 Tallest Cow

差分前缀和

POJ1050 To The Max

求最大子矩阵 O(n3)

经典连续字段和,枚举两行,转化两行间矩阵的的最大连续字段和,枚举两行n2,枚举列n,共计n3

https://www.cnblogs.com/fll/archive/2008/05/17/1201543.html 说的很好很详细

for(i=0;i<n-1;++i)
            {
                for(j=i+1;j<n;++j)
                {
                    tmp=0;
                    for(k=0;k<n;++k)
                    {
                        //相当于把子矩阵多行压缩为一行了
                        m[i][k]+=m[j][k];
                        if(tmp>0)tmp+=m[i][k];
                        else tmp=m[i][k];
    
                        if(tmp>max)max=tmp;
                    }
                }
    
            }
            printf("%d\n",max);

裸最大连续字段和

     int maxSubArray(int n,int a[])
        {
            int b=0,sum=-10000000;
            for(int i=0;i<n;i++)
            {
                 if(b>0) b+=a[i];
                 else b=a[i];
                 if(b>sum) sum=b;  
            }
            return sum;
        }

POJ2018 Best Cow Fences

求数列中一段平均数最大,长度不小于L的连续字段
二分答案,把数列中每个数减去二分答案值,就转化为判定“是否存在一个长度不小于L的字段,字段和非负
1.不考虑 “长度不小于L” 这个限制 O(n)

在日本人写的书里初见这道题好像没有长度的限制 单独拿出来先说

其实直接转化为了最大连续字段和的裸题 ,直接参照上方的 POJ1050。

2.考虑 “长度不小于L” 这个限制.
用前缀和 O(n)检验二分答案的可行性
单调性why?斜率相关,留个坑。

AcWing 113 特殊排序

交互题:询问 1e4 次求得有向图拓扑序 n<=1e3
数学归纳法证明:k-1 有序时,插入第k个可二分,水题,总询问数 NlogN

CF 670C Cinema

排序+离散化,记得离散化的时候把输入的三个数组的数都放进一个数组离散就好了,反正是一起查询。离散化可以用 sort + unique + erase 也可下面代码写法,难度是不大。

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#define INF 0x3f3f3f3f
using namespace std;
const int maxn=2e5+5;
int n,m;
int a[maxn],b[maxn],c[maxn];
int sum[maxn*3];
int cnt,mm;
int arr[maxn*3],num[maxn*3];
void discrete(){//离散化
    sort(arr+1,arr+cnt+1);
    for(int i=1;i<=cnt;i++){
        if(i==1||arr[i]!=arr[i-1])
            num[++mm]=arr[i];
    }
}
int query(int x){//二分查找x的位置
    return lower_bound(num+1,num+mm+1,x)-num;
}
int main(){
    scanf("%d",&n);
    for(int i=1;i<=n;i++){//将所有电影和人涉及的语言放进一个数组,排序并离散化
        scanf("%d",&a[i]);
        arr[++cnt]=a[i];
    }
    scanf("%d",&m);
    for(int i=1;i<=m;i++){
        scanf("%d",&b[i]);
        arr[++cnt]=b[i];
    }
    for(int i=1;i<=m;i++){
        scanf("%d",&c[i]);
        arr[++cnt]=c[i];
    }
    discrete();//离散化
    for(int i=1;i<=n;i++){
        int id=query(a[i]);//统计每种语言的人的数量
        ++sum[id];
    }
    int bmax=-1,cmax=-1,ans=0;
    for(int i=1;i<=m;i++){//选择满足题目要求的电影
        int x=query(b[i]);
        int y=query(c[i]);
        if(sum[x]>bmax){//优先考虑让很高兴的人最多
            bmax=sum[x],cmax=sum[y];
            ans=i;
        }else {
            if(sum[x]==bmax&&sum[y]>cmax){//如果答案不唯一、则在此前提下再让比较高兴的人最多
                bmax=sum[x],cmax=sum[y];
                ans=i;
            }
        }
    }
    printf("%d\n",ans);
    return 0;
}

CH 0501 货仓选址

东北赛,中位数性质

POJ 3784 Running Median

动态维护中位数问题

“对顶堆”

在线做法:维护两个二叉堆,一个大根堆,一个小根堆,始终保持
1.序列中从小到大排名为 1~M/2 的整数存储在大根堆中
2.序列中从小到大排名为 M/2+1~M 的整数存储在小根堆中
任何时候,如果一个堆中元素个数过多,打破了这个性质,就取出该堆的对顶插入另一个堆

求第k大数

类似快速排序的思想,O(n)

POJ 2299 Ultra-QuickSort

其实就是求逆序对数,归并排序解决。

CH 0503 奇数码问题
其实可以巧妙的转化为两个状态平铺开来,转化为状态逆序对数是否相同,因为上下还是左右不改变逆序对奇偶性,必要性很好证明,充分性假装证明了。
转化为n为偶数的情况,有“逆序对数之差”和“两个局面下的空格所在行数之差”奇偶性相同。
再拓展到 n * m 的情况,根据列数奇偶性分情况讨论推到即可。

总而言之,n * m的数码问题的有解性判定,可以转化为归并排序求逆序对数来解决。

CH 0601 Genius ACM

传送门:ACM-ICPC Beijing 2016 Genius ACM(倍增+二分)

数据范围与约定
T≤12
1≤n,m≤5e5
0≤k≤1e18
0≤Pi≤2e20

当左端点L确定下来时,如果在L~N上二分R,其实是不合适的,因为T的值比较小,很可能R只是扩展了很短的一点点,所以需要一个与右端点R扩展长度比较匹配的算法,在这里考虑倍增。
1.初始化 p=1,R=L
2.求出 [L,R+p] 这一段区间的“校验值”,若“校验值”<=T,则R+=p,p*=2,否则p/=2。
3.重复上一步操作,直到 p 的值变为0,此时 R 即为所求。
总体时间复杂度为 O(Nlog2N)

如果排序不用快排,而采用归并排序对新增的长度部分排序,然后合并新旧两段,总复杂度可降到 O(NlogN)

POJ 3614 Sunscreen

贪心:奶牛排序,每只奶牛枚举一边防晒霜,找能用的防晒霜里SPF值最大的。

POJ3190 Stall Reservation

贪心:牛的开始时间排序,可以开一个堆,一旦没有能满足的就开新堆,多个满足的选择结束时间较早的。

POJ 1328 Radar Installation

贪心,坐标,区间

NOIP2012/CH0701 国王游戏

贪心,左右手乘积排序,可以使用微扰(邻项交换)证明

POJ 2054 Color a Tree

题意:有一棵树,每个节点都有一个代价基值Ci。现在要给每个点染色,第一个染根节点,其余的节点染色的时候其父节点必须已染色。每个节点染色会用掉一个时间单位,每个节点染色的代价是染完此节点时的总时间T*Ci。问染完全部节点所需要的最小代价。
贪心+难+好题 分析如下:

试想,如果没有父节点排在节点之前的限制,那么这个题目非常简单,只需要将结点按照权值从大到小排列即可。加上了这个限制之后,如果权值最大的那个节点一旦满足了条件(父节点被排在了之前的某个位置),那么这个权值最大的节点一定要紧挨着这个父节点,即把这个权值最大的节点排在它所能排的最前面的位置。因为对于这个节点如果不受限制应该排在第一位,而有了限制,在满足了限制之后也应把它尽可能地排在前面。所以它一定是挨着父节点的。那么现在在最终的排列中我们确定了两个节点的前后相邻关系,将他们绑定在了一起。

试想如果保持这个相邻关系的同时去掉其他节点的限制,那么我们应该如何排列呢?我们假设绑定在一起的两节点是a和b。现有一个另外的节点x,我们看两种排列xab,abx对最终的计算结果有什么影响。xi+a(i+1)+b*(i+2);
ai + b(i+1) +
x*(i+2)。后者减去前者等于2x-(a+b)。即将x从ab之前挪到ab之后,ab各左移1位,结果减小a+b。x右移2位结果增加2x。因此两者谁在前谁在后我们只需要比较a+b和2x即可,也可以比较(a+b)/2和x。

将这个定理进行一下推广,绑定在一起的不一定是两个节点,可以是一个更长的序列,与这个序列进行比较看谁放在前面的也可以是一个序列。设一个序列有n1个节点,第二个序列有n2个节点。那么我们比较两者谁放在前面的时候需要比较的是(n1个权值之和×n2)和(n2个权值之和×n1)。即左移和右移产生的结果变化。当然也可以比较(n1个权值之和/n1)和(n2个权值之和/n2)。

我们可以再次进行推广,如果我们要排列的不是节点,而是许多序列的话,那么我们只需要计算每个序列权值的平均数(例如:n个节点的序列,要计算n个权值之和/n),然后按照这个平均数从大到小排列即可使得计算结果最小。这样就可以让序列与节点有了一个统一的衡量值——平均数。

这样一来,我们就可以将上面的绑定两节点的操作看成是将问题规模缩小的操作,在帮定两节点的同时我们在树中也将两节点合并,变为一个节点,即将子节点的孩子变为父节点的孩子。然后合并后的节点的权值是合并在这个节点中的所有节点的权值的平均数。我们成功的将问题规模减小了1。只需要不断这样做即可将问题缩减为只有一个节点。

//贪心:如果这棵树中有最大权值点X(非根),
//那么一旦X的父节点Y已经染色,就应该立刻染X
//于是X和Y合并成一个点集,
//新点集的权值=(新点集中所有点的权值和)/(新点集中点的个数)
//类似的,该点集可以看为一个点
//重复上述贪心思路,直到最后只剩下一个根r点集
#include
#include
#include
#include
#include
using namespace std;

const int N=1005;

struct Node
{
    int f;//父结点
    int t;//时间
    int c;//原权值
    double w;//贪心权值(c/t)
}node[N];

int n,r;

int findPos()
{
    int pos;
    double wmax=0;
    for(int i=1;i<=n;i++)
        if(node[i].w>wmax&&i!=r)
        {
            wmax=node[i].w;
            pos=i;
        }
    return pos;
}

int main()
{
    while(scanf("%d%d",&n,&r)&&n+r)
    {
        int pos,fa,res=0;
        for(int i=1;i<=n;i++)
        {
            scanf("%d",&node[i].c);
            node[i].w=node[i].c;//将贪心权值w初始化为原权值c
            node[i].t=1;//时间初始化为1
            res+=node[i].c;//结果初始化为sum of所有原权值c
        }
        int u,v;
        for(int i=1;i<n;i++)
        {
            scanf("%d%d",&u,&v);
            node[v].f=u;//记录父结点
        }

        for(int i=1;i<n;i++)
        {
            pos=findPos();//找到贪心权值最大的位置
            node[pos].w=0;//置0,以便下次查找时跳过之
            fa=node[pos].f;//fa为pos的父结点
            res+=node[pos].c*node[fa].t;//res+=pos原权值*fa父结点时间(node[fa].t实际上代表,点集中有几个点)
            for(int j=1;j<=n;j++)
                if(node[j].f==pos)
                    node[j].f=fa;//若有j的父结点是pos,则将j的父结点改为fa,建立新集合
            node[fa].t+=node[pos].t;//更新以fa为父结点的点集内点的个数(即加上以pos为父结点的点集内点的个数)
            node[fa].c+=node[pos].c;//更新点集的话,那权值自然也要更新
            node[fa].w=(double)node[fa].c/node[fa].t;//贪心原则:新点集权值和/新点集中点的个数
        }
        printf("%d\n",res);
    }
    return 0;
}

你可能感兴趣的:(震惊!!只要刷了这些题,你也学不会ACM!!)