[SLYZ]20180310省选hu测D1

本文解析了三道经典的算法题目,包括树叶收集问题、多边形对称轴计数及序列排序验证,涵盖动态规划、搜索算法及线段树等核心技术。

这里写图片描述
出题人:翔哥

题目背景

当年还在OI 赛场驰骋的他,就被称为老师,如今他已经成为了名教练。为了促进偏远地区OI 的发展,S 老师走入山区开始了他的支教生活。

1、收集树叶 (collect.pas/c/cpp)

题目描述

从山顶到山脚沿着一条线种植了 n 棵老树,有法桐、珙桐、Wu桐、SW 桐。秋天到了,这些 tree 开始掉 leaves 了,村长Loli要把这些珍贵的树叶收集起来加工成药材。这些树叶只能按照一个方向运输——朝山下运。村长Loli找来了博学的 S 老师,S 老师建议用船运输,因为 tree很 loves 山上的water。然而用船运输是要花一定的费用的,运输费为1¥/(斤·米)。正当村长Loli为运输费发愁时,政府决定在山上建两个额外的加工厂,树叶被运到某个加工厂就不需要再往下运输了,这样就能节省一笔运输费了。这可把村长Loli高兴坏了,赶忙找来S 老师帮忙算一下如何建厂才能使总运输费最小。S 老师当然会算了,请你也来算一算最小的总运输费是多少。

输入格式

输入的第一行为一个整数——树的个数。树从山顶到山脚按照1,2…n 标号。接下来 n 行每行有两个正整数,第 i+1 行的两个正整数分别表示第i 棵树掉落的叶子的重量(斤)和第 i 棵树与第 i+1 棵树的距离(米) ,特殊的,第 n+1 行的第二个数表示第 n 棵树与山脚的加工厂的距离(米) 。

输出格式

输出只有一行一个数,表示最小的运输费用(¥)

输入样例
9    
1 2    
2 1    
3 3    
1 1    
3 2    
1 6    
2 1    
1 2    
1 1  
输出样例
26  
数据规模与约定

对于 20%的数据,1≤n≤200
对于 40%的数据,1≤n≤1000
对于 100%的数据,1≤n≤20000
每棵树掉落的叶子的重量不会超过 5000(斤) 。
任意两棵树之间的距离不会超过 5000(米) ,且有 50%的数据满足任意
两棵树之间的距离为 1(米) 。

解题报告

心力憔悴,不想写题解

#include <cstdio>
#define N 20010
#define COST(i, j) (cost[j] - cost[i] - prodS[i] * (dis[j] - dis[i]))

inline int min(const int &a, const int &b)
{
    return a < b ? a : b;
}
int n,dis[N],prod[N],prodS[N];
long long cost[N],f[N][3],ans;

int main()
{
    scanf("%d", &n);
    for (int i = 1; i <= n; i++)
    {
        scanf("%d %d", &prod[i], &dis[i + 1]);
        prodS[i] = prodS[i - 1] + prod[i];
    }
    for (int i = 2; i <= n + 1; i++)
        cost[i] = cost[i - 1] + dis[i] * prodS[i - 1];
    for (int i = 2; i <= n + 1; i++)
        dis[i] += dis[i - 1];
    dis[1] = 0;
    //在i上建厂后再往j上建后到i + 1 -> j的花费cost(i, j) = cost[j] - cost[i] - prodS[i] * (disS[j] - disS[i] - (j - i) * dis[i])
    //f[i][j]: 在i处建厂, 在此前已经建了j个处理厂了 
    //f[i][j] = min{cost(k, i) + f[k][j - 1]} (1 | j - 1 <= k <= i)
    for (int i = 2; i <= n + 1; i++)
    {
        f[i][1] = COST(1, i);
        for (int j = 2; j < i; j++)
            f[i][1] = min(f[i][1], cost[j] + COST(j, i));
        f[i][2] = COST(2, i);
        for (int j = 3; j < i; j++)
            f[i][2] = min(f[i][2], f[j][1] + COST(j, i));
    }
    printf("%lld", f[n + 1][2]);
    return 0;
}

斜率优化

#include<cstdio>
typedef long long LL;
const int N=2e4+7;
int n;
LL s[N],dis[N],cost[N],d[N],ans=2e9+7;
int q[N],head,tail;

inline bool pd1(int k,int j,int i){
    return s[j]*dis[j]-s[k]*dis[k]<=dis[i]*(s[j]-s[k]);
}

inline bool pd2(int k,int j,int i){
    return (s[j]*dis[j]-s[k]*dis[k])*(s[i]-s[j])>(s[i]*dis[i]-s[j]*dis[j])*(s[j]-s[k]);
}

int main(){
    scanf("%d",&n);
    for(int i=1,x;i<=n;++i)
    {
        scanf("%d%lld",&x,&d[i]);
        s[i]=s[i-1]+x;
        dis[i]=dis[i-1]+d[i-1];
        cost[i]=cost[i-1]+s[i-1]*d[i-1];
    }
    dis[n+1]=dis[n]+d[n];
    cost[n+1]=cost[n]+s[n]*d[n];
    q[0]=0;
    for(int i=1,j,f;i<=n;++i)
    {
        while(head<tail&&pd1(q[head],q[head+1],i)) ++head;
        j=q[head];
        f=-s[j]*(dis[i]-dis[j])+cost[n+1]-s[i]*(dis[n+1]-dis[i]);
        if(f<ans) ans=f;
        while(head<tail&&pd2(q[tail-1],q[tail],i)) --tail;
        q[++tail]=i;
    }
    printf("%lld",ans);
}

第二种做法是随机化,方法出自《浅谈随机化在信息学竞赛中的应用》(刘家骅)
我们建立一个矩阵P,P[X,Y]表示第一个锯木场建立在X,第二个锯木场建立在Y时的总运费。一开始时,矩阵的边长为N。我们随机寻找一定数量的点(如下左图所示,取点数量应该充分利用时限并且注意效率,由于矩阵的大小一直在变化,推荐使用矩阵大小的定比确定取点数量),计算出它们的值,取其最小点,以这个点为新矩阵的中心,以现在矩阵的边长的3/4的长度为新矩形的边长
(如下右图所示),从原来的矩阵中取出一块作为新矩阵的范围(若新矩阵的范围出了原矩阵的边界就将其向里移动到原矩阵内) ,然后继续在新矩阵中重复这样的操作,直至新矩阵足够小时,我们即可枚举新矩阵上的每一个点,取其中最小值作为答案。
这里写图片描述
题目来源 :[CEOI 2004] Two sawmills

2、数学作业 (homework.pas/c/cpp)

题目描述

数学课上,S 老师教会了小朋友们画多边形。下课后,S 老师给小朋友们留了一个作业——数多边形的对称轴的数量。小朋友们都不会做,抱怨道“S老师太毒瘤了”,纷纷来找你帮忙,你能帮小朋友们按时完成作业吗?为了在小朋友面前不失风度,你需要在 1s 内解决每个问题。

输入格式

第一行一个整数 T ,表示你需要数出 T 个多边形的对称轴的数量。接下来有 T 组数据来描述这 T 个多边形。对于每组数据:
第一行一个整数 n ,表示这个多边形的顶点数。
接下来 n 行,每行两个整数 x、y ,描述一个顶点的坐标。
顶点沿多边形的边连续给出。
多边形不保证是凸多边形。
若两条边有公共顶点,保证这两条边不共线。(废话)

输出格式

输出 T 行,每行 1 个整数,表示每个多边形的对称轴的数量。

输入样例
2
12
1 -1
2 -1
2 1
1 1
1 2
-1 2
-1 1
-2 1
-2 -1
-1 -1
-1 -2
1 -2
6
-1 1
-2 0
-1 -1
1 -1
2 0
1 1
输出样例

4
2

数据规模与约定

对于 100%的数据,满足
1≤T≤10,3≤n≤100000,-100000000≤x,y≤100000 000

解题报告

通这道题就练练搜索吧,有几个学长通过搜索A了这道题,除了zyz写的正解
题目来源 :[POI 2007] Axes of Symmetry

#include<iostream>
#include<cstdio>
#include<cstring>
#define cdd(x) ((x)*(x))
#define re register
using namespace std;

int next[300001],n,x[1000001],y[1000001],bian[1000001],map[1000001],tot;

int main()
{
    freopen("homework.in","r",stdin);
    freopen("homework.out","w",stdout);
    int T;
    scanf("%d",&T);
    while(T--)
    {
        int ans=0,tot=0;
        memset(next,0,sizeof(next));
        scanf("%d",&n);
        for(re int i=1;i<=n;i++)
            scanf("%d%d",&x[i],&y[i]);
        x[n+1]=x[1]; y[n+1]=y[1]; x[n+2]=x[2]; y[n+2]=y[2];
        for(re int i=1;i<=n;i++)
        {
            bian[++tot]=cdd(x[i+1]-x[i])+cdd(y[i+1]-y[i]);
            bian[++tot]=(y[i+2]-y[i])*(x[i+1]-x[i])-(x[i+2]-x[i])*(y[i+1]-y[i]);
        }

        for(re int i=1;i<=tot;i++)
        {
            map[tot-i+1]=bian[i];
            bian[i+tot]=bian[i];
        }
        for(re int i=2,j=0;i<=tot;i++)
        {
            while(j && map[j+1]!=map[i]) j=next[j];
            if(map[j+1]==map[i]) j++;
            next[i]=j;
        }
        for(re int i=1,j=0;i<=tot*2;i++)
        {
            while(j && map[j+1]!=bian[i]) j=next[j];
            if(map[j+1]==bian[i]) j++;
            if(j==tot) ans++,j=next[j];
        }
        printf("%d\n",ans);
    }
    return 0;
}

还有一种思路没有调出来:

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
using namespace std;
#define ll long long
const int maxn=1e5;
int T,n,x,y,ans;
struct Dian{
    double x,y;
}dian[maxn];

double Abs(double x) {return x<0?-x:x;}
int mod(int x) {if (x>n) x-=n; if (x<=0) x+=n; return x;}
double calc(int i,int j) {
    printf("%.2lf  %.2lf\n",Abs(dian[i].y-dian[j].y),Abs(dian[i].x-dian[j].x));
     return Abs(dian[i].y-dian[j].y)/Abs(dian[i].x-dian[j].x);

    }//防止负数 
bool same(double x,double y) {return x-0.001<=y&&y<=x+0.001;}

void work(int a)
{
    bool flag=1;
    printf("%d  %d\n",mod(a+1),mod(a-1));
    double k=calc(mod(a+1),mod(a-1));
    printf("%.2lf %.2lf\n",dian[mod(a+1)].x,dian[mod(a+1)].y);
    for (int i=2; i<=(n&1)?n/2:n/2-1; i++)
    {
        printf("%d  %d\n",mod(a+i),mod(a-i));
        double k1=calc(mod(a+i),mod(a-i));
        if (!same(k,k1)) {flag=0; break;}
    }
    if (flag) ans++; flag=1;
    if (!(n&1))
    {
        k=calc(mod(a),mod(a+1));
        printf("%d  %d\n",mod(a),mod(a+1));
        for (int i=1; i<=n/2-1; i++)
        {
            printf("%d  %d\n",mod(a-i),mod(a+1+i));
            double k1=calc(mod(a-i),mod(a+1+i));
            if (!same(k,k1)) {flag=0; break;}
        }
    }
    if (flag) ans++;
}

int main()
{
//  freopen("homework.in","r",stdin);
//  freopen("homework.out","w",stdout);

    scanf("%d",&T);
    while (T--)
    {
        ans=0;
        scanf("%d",&n);
        for (int i=1; i<=n; i++)
            scanf("%lf%lf",&dian[i].x,&dian[i].y);
        for (int i=1; i<=n; i++)
        {
            work(i);
        }
        printf("%d\n",ans/2);
    }
    return 0;
}

3 、刁难老师 (joke.pas/c/cpp)

题目描述

体育课上,S 老师看见小朋友们在玩游戏。S 老师很想加入他们, 就走过去跟小李说:“我能和你们一起玩吗。”小刘站出来说:“你要是通过了我的考验,我就让你和我们一起玩。”小刘给出 m个区间, [L1,R1]…[Lm,Rm],然后进行 q 轮游戏,每轮游戏小刘会给 n 个排好队的小朋友每人分配一个编号(编号可重复,且不必是连续整数的排列),然后让 S 老师脑补如下过程:对位置在[L1,R1]的小朋友按照编号升序排序,对位置在[L2,R2] 的小朋友重复这一过程…对位置在 [Lm,Rm]的小朋友重复这一过程,最后 S 老师需要回答最终小朋友们是否按照编号升序排序。S 老师听完游戏规则后,轻蔑地一笑,说:
“你们啊,too young,too simple,sometimes naive,我思考的总时间加起来都到不了 2s。”S 老师可搞了个大新闻,为了不被小朋友们批判一番,他请你来帮忙。

输入格式

第一行三个整数 n、m、q,分别表示人数、区间数、游戏轮数。
接下来 m 行按顺序给出每个区间[Li ,Ri]。
接下来 q 行,每行 n 个整数,a[1],a[2]…a[n],表示这一轮游戏中每个人的编号。

输出格式

对每轮游戏输出一行,若能升序排序,输出“AC”,否则输出“WA”。
(不输出引号)。

输入样例
6 3 2  
1 3  
3 6  
1 3  
4 2 2 3 0   
5 3 2 1 9  
输出样例
AC  
WA  
数据规模与约定
编号      n≤           m≤            q≤             备注  

1        100           100           100             
2        200           200           200             
3        1400          900000        8               
4        1500          1000000       8               
5        1400          4000          1400            
6        1500          5000          1500           
7        1200          00000         1200           
8        1300          00000         1300         满足 a[i]非 0 即 1,且 1 和 0 的数量相等  
9        1400          900000        1400         此测试点 20 分


                                                std常熟巨大,AK的dalao可以放心蹂躏 
题解

std:
线段树+(平衡树||STL:set)
3.1 朴素算法:O(qmn log n)
3.2 预备技能——消逆序对排序:O(q(m+n²)log n)
对于一个序列,找到其中一个位置i满足a[i]>a[i+1],交换a[i]和a[i+1],重复这样的做法,直到不存在满足条件的 i,则序列被排好序。交换的次数等于逆序对的数量,而数量是 n²级别的。对于某一轮游戏,可以每次在待排序区间[Li,Ri]内消逆序对。如果某个区间内的逆序对很少,那扫一遍区间是很浪费时间的。可以把满足条件的位置 i 插入平衡树,对[Li,Ri]这个区间进行消逆序对时只需要在平衡树中把[Li,Ri)内的点依次取出,对序列修改后若产生新的满足条件的位置再插入即可。其中平衡树可以使用 STL:set,只有 insert,erase,lower_bound三种操作。
在 m较大 n较小时, (m+n² )log n的排序优于 mnlog n 的排序,但 q 较大的时候依然会超时,只能通过4 号点。
3.3 转换——01 序列

若要检验一个 1~n 的排列能否被这 m 次操作排好序,不需要对 n!种排列进行检验,只需要对 2ⁿ种 01序列进行检验即可。这是排序网络的 0-1原理,它的正确性很容易证明。

3.4 统计——逆向递推
下面研究具有什么性质的初始序列是可以被排序的。先从 01序列的特殊情况入手。一个最终被排好序的序列就是前面一段 0,后面一段1,我们可以把[Lm,Rm]这段中的数以任意方式打乱,得到的序列都是可通过第 m 次操作排好序的序列。我们逆向递推,理论上就能得到每一 次操作后能被以后的操作排好序的所有序列。
然而合法序列的个数是指数级的。

3.5 精简——代表元素
若一个形如 111000 的序列能被正确排好序,那么 101010必然也能被正确排好序。我们需要一个对序列“好坏”程度的衡量标准,0 越靠左,1 越 靠右,序列越接近升序,那么这个序列就越好。因此容易发现,对区间[Li,Ri]打乱顺序的最坏方式就是把 1都放到左边,0都放到右边。对于一个序列 a,用 pos(a,i)表示 a 中从左到右第 i个 1所在的位置,若 a 比 b优秀,当且仅
当对于i=1,2,…,k都有 pos(a,i)≥pos(b,i)。
于是合法序列集的递推转化为了合法的最差元素的递推。

3.6 一步之遥——针对8 号点的算法:O((m+n²)log n + qn)
8号点是 01序列。先对序列使用消逆序对的方法排序,然后对 q 个询问每个 O(n)检验。

3.7 满分算法:O((m+n²+qn)log n)
不妨假设待排序的数组A 是一个 n排列。如果不是的话,可以将它先离散化,其中值相同的元素按照下标递增的顺序分配。容易看出这样并不会影响最终的答案。
现在尝试将 01序列的算法推广到 n排列。对于初始排列 A[1…n]和给定的 k,定义 01 序列 Bk,其中 Bk[i]=1 当且仅当A[i]≥k。之前已经提到过, 算法能将 A 数组正确排序,当且仅当对于所有 k=2,3,…,n 该算法都能将 01序列 Bk 正确排序。

根据刚才的算法,需要对每个 k,预处理出 n-k 个 0 后跟 k 个 1 的串倒着进行 m 次操作后的结果 Ck。直接做肯定太慢了,我们可以做一件等价的事情:令初始排列为 1,2,…,n,按 i从 m 到 1的顺序,每次将[Li,Ri]降序排序。
最后得到的序列中,将小于 k 的值用 0代替,其他用 1代替,得到的 01 序列就是所求的 Ck,且Ck 的某个位置的 0 改为 1就可以得到 Ck-1 。
现在要对所有 k=2,3,…,n 检查 Bk 是否比 Ck 优秀,即 Bk 的任何一个前缀的 1都不能比 Ck 对应的前缀的 1 多。如果我们把 Bk 中的 1取负号,在求完 k=i 然后对 k=i-1 求解时,就是加入一对新的-1和+1,然后检查数组的每一个前缀和是否都非负,这个过程可以用线段树的区间加、维护区间最小值来实现(线段树维护前缀和序列)。

预处理过程依然用消逆序对的算法实现。可通过 100%的数据。

题目来源 :[2016 国家集训队互测] 基础排序算法练习题 (金策)

#include<set>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;

#define FOR(i,l,r) for(int i=l;i<=r;++i)
#define REP(i,l,r) for(int i=l;i<r;++i)
#define fo(i,r,l) for(int i=r;i>=l;--i)

inline int read(){
    register int ss=0;
    register char chr=getchar();
    while(chr<'0'||chr>'9') chr=getchar();
    while('0'<=chr&&chr<='9') ss=(ss<<3)+(ss<<1)+chr-'0',chr=getchar();
    return ss;
}

const int N=1507,M=1e6+7;
int n,m,q,fix_l[M],fix_r[M],b[N],c[N],posc[N],posb[N];
int minn[N<<2],tag[N<<2],L,R,x;
set<int>s;

inline void Sort(int l,int r)
{
    int i=*s.upper_bound(l-1);
    while(l<=i&&i<r)
    {
        s.erase(i);
        c[i]^=c[i+1]; c[i+1]^=c[i]; c[i]^=c[i+1];
        if(c[i+1]<c[i+2]&&c[i]>c[i+2]) s.insert(i+1);
        if(c[i-1]<c[i]&&c[i-1]>c[i+1]) s.insert(i-1);
        i=*s.upper_bound(l-1);
    }
}

struct LSH{int val,id;}f[N];
inline bool cmp(LSH xx,LSH yy)
{
    return xx.val<yy.val||(xx.val==yy.val&&xx.id<yy.id);
}
inline void Input()
{
    FOR(i,1,n) f[i].val=read(),f[i].id=i;
    sort(f+1,f+n+1,cmp);
    FOR(i,1,n) b[f[i].id]=i;
}

inline void pushdown(int u,int l,int r)
{
    int ls=u<<1,rs=u<<1|1;
    minn[ls]+=tag[u]; minn[rs]+=tag[u];
    tag[ls]+=tag[u]; tag[rs]+=tag[u];
    tag[u]=0;
}

void fix(int u,int l,int r)
{
    if(L>r||l>R) return;
    if(L<=l&&r<=R)
    {
        minn[u]+=x;
        tag[u]+=x;
        return;
    }
    if(tag[u]) pushdown(u,l,r);
    int mid=(l+r)>>1;
    fix(u<<1,l,mid); fix(u<<1|1,mid+1,r);
    minn[u]=min(minn[u<<1],minn[u<<1|1]);
}

int main(){
    freopen("joke.in","r",stdin);
    freopen("joke.out","w",stdout);
    n=read(); m=read(); q=read();
    FOR(i,1,m) fix_l[i]=read(),fix_r[i]=read();
    FOR(i,1,n) c[i]=i;
    REP(i,1,n) s.insert(i);
    fo(i,m,1) Sort(fix_l[i],fix_r[i]);
    FOR(i,1,n) posc[c[i]]=i;
    while(q--)
    {
        Input();
        FOR(i,1,n) posb[b[i]]=i;
        memset(minn,0,sizeof(minn));
        memset(tag,0,sizeof(tag));
        int flag=true;
        FOR(k,2,n)
        {
            L=posb[k-1]; R=n; x=1;
            fix(1,1,n);
            L=posc[k-1]; R=n; x=-1;
            fix(1,1,n);
            if(minn[1]<0) {flag=false; break;}
        }
        flag ? printf("AC\n") : printf("WA\n");
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值