NOIP练习赛题目4

肥得更高
难度级别:C; 运行时间限制:1000ms; 运行空间限制:51200KB; 代码长度限制:2000000B
试题描述

自2009年以来,A、B站的历史就已经步入了农业变革的黎明期。在两站的娱乐及音乐区,金坷垃制造业早已得到长足的发展,甚至有些地方还出现了坷垃翻唱的萌芽。新兴肥料人开始走上历史的舞台。他们需要新的意识形态,来为他们所追求的肥料辩护;他们需要新的理念、新的手段,来为金坷垃的生产提供支持。这样,一种崭新的肥料精神就诞生了。肥料复兴,是反对肥料粗制滥造,追求创新的新肥料文化的运动。它必将成为推动金坷垃走得更远、飞得更高的重要力量。
现在,你有n亩的小麦地需要增产,你拥有一些金坷垃,但是金坷垃极其稀少,掺肥料也只够你撒K次。众所周知,金坷垃能激活土壤深处的氮磷钾,同一块地可以撒多次肥料,但是效果是有略微衰减的。实地考察后你发现,第i亩土地第x次撒肥料增产a[i]-x+1公斤。hzwer将代替你去撒肥料,但是他是个蒟蒻,完全不动大脑,所以你想知道如果他随机撒肥料,最坏情况下小麦将增产多少,最好情况下将增产多少?(他最多只会对第i亩地撒肥料a[i]次)

输入
第一行两个整数n,K
第二行n个整数,第i个整数为a[i]
输出
输出最大值,最小值,空格隔开
输入示例
5 10
10 3 3 1 2
输出示例
58 26
其他说明
对于30%的数据n,k<=1000
对于70%的数据n,k<=200000
对于100%的数据n,k,a[i]<=1000000

最大值可以这么求:模拟k次操作,每次选出最多的a[i]来撒。

最小值可以这么求:模拟k次操作,每次选出最小的a[i]来撒。

然后因为权值范围不大,几个操作都可以O(c)来做。

#include<cstdio>
#include<cctype>
#include<queue>
#include<cmath>
#include<cstring>
#include<algorithm>
#define rep(i,s,t) for(int i=s;i<=t;i++)
#define dwn(i,s,t) for(int i=s;i>=t;i--)
#define ren for(int i=first[x];i!=-1;i=next[i])
using namespace std;
inline int read() {
    int x=0,f=1;char c=getchar();
    for(;!isdigit(c);c=getchar()) if(c=='-') f=-1;
    for(;isdigit(c);c=getchar()) x=x*10+c-'0';
    return x*f;
}
typedef long long ll;
const int maxn=1000010;
int n,k,cnt,A[maxn],S[maxn];
ll ans1,ans2,t;
int main() {
    n=read();k=read();
    rep(i,1,n) S[A[i]=read()]++,t+=A[i];
    if(k>t) k=t;
    int cur=1000000;
    rep(i,1,k) {
        while(!S[cur]) cur--;
        ans1+=cur;S[cur]--;S[cur-1]++;         
    }
    rep(i,1,n) S[A[i]]=0;
    rep(i,1,n) S[A[i]]++;
    rep(i,1,1000000) while(S[i]--) A[++cnt]=i;
    rep(i,1,1000000) if(A[i]>=k) {
        ans2+=(ll)k*(A[i]+A[i]-k+1)/2;
        break;
    }
    else {
         ans2+=A[i]*(A[i]+1)/2;
         k-=A[i];     
    }
    printf("%lld %lld
",ans1,ans2);
    return 0;    
}
View Code
传教士(bishop)
难度级别:C; 运行时间限制:1000ms; 运行空间限制:51200KB; 代码长度限制:2000000B
试题描述

panzhili王国的疆土恰好是一个矩形,为了管理方便,国王jjs将整个疆土划分成N*M块大小相同的区域。由于jjs希望他的子民也能信教爱教(”打拳”神教),所以他想安排一些传教士到全国各地去传教。但这些传教士的传教形式非常怪异,他们只在自己据点周围特定的区域内传教且领地意识极其强烈(即任意一个传教士的据点都不能在其他传教士的传教区域内,否则就会发生冲突)。现在我们知道传教士的传教区域为以其据点为中心的两条斜对角线上(如图)。现在jjs请你帮忙找出一个合理的安置方案,使得可以在全国范围内安置尽可能多的传教士而又不至于任意两个传教士会发生冲突。

X

X

X

A

X

X

(若A为某传教士的据点,则其传教范围为所有标有X的格子。为不产生冲突,则第二个传教士的据点只能放在上图的空格中。)

输入
输入文件共一行,包含两个整数N和M,代表国土的大小,n为水平区域数,m为垂直区域数。
输出
输出文件仅一行,包含一个整数,即最多可以安置的传教士的数目。
输入示例
3 4
输出示例
6
其他说明
说明:样例安置方案如下图所示,X表示为某传教士的据点。
XXX
OOO
OOO
XXX
对于100%的数据,1<=n,m<=9,且数据规模呈梯度上升。

先写个暴力,然后打表找结论。

#include<cctype>
#include<queue>
#include<cmath>
#include<cstring>
#include<algorithm>
#define rep(i,s,t) for(int i=s;i<=t;i++)
#define dwn(i,s,t) for(int i=s;i>=t;i--)
#define ren for(int i=first[x];i!=-1;i=next[i])
using namespace std;
inline int read() {
    int x=0,f=1;char c=getchar();
    for(;!isdigit(c);c=getchar()) if(c=='-') f=-1;
    for(;isdigit(c);c=getchar()) x=x*10+c-'0';
    return x*f;
}
int main() {
    int n=read(),m=read();
    if(n==1&&m==1) {puts("1");return 0;}
    if(n>m) swap(n,m);
    if(n&1) printf("%d
",n+m-1-(n==m));
    else printf("%d
",n+(m-1)/2*2);
    return 0;
}
View Code
cyz把妹
难度级别:C; 运行时间限制:1000ms; 运行空间限制:262144KB; 代码长度限制:2000000B
试题描述

Czy是个大丧失,非常喜欢bm。他经常挑战bm的极限,同时b很多的mz。(虽然也许质量不容乐观)
这一天,czy又开始了他的极限挑战。在一个数轴上有n个maze,她们都在等待着czy的到来。Czy一开始站在k号妹子的旁边,他需要搞定所有的妹子(由于他向fewdan学会了绝技,所以搞定妹子的时间是无限接近于0的,也就是一瞬间就搞定而不用花额外的时间)。Maze们都很没有耐心,每让她们多等1s,她们就会增加w[i]的不开心值。现在,czy从k号妹子这里出发,以1m/s的速度开始行动,他希望在搞定所有maze的情况下使得她们的不开心值总和最小,于是他找到了即将在NOIP2015 AK的你来帮他解决这个问题。

输入
输入文件的第一行包含一个整数N,2<=N<=1000,表示maze的数量。
第二行包含一个整数V,1<=V<=N,表示开始时czy站在几号maze的旁边.接下来的N行中,每行包含两个用空格隔开的整数D和W,用来描述每个maze,其中0<=D<=1000,0<=W<=1000。D表示MM在数轴上的位置(单位: m),W表示每秒钟会增加的不开心值。
输出
一个整数,最小的不开心值。(答案不超过10^9)
输入示例
4
3
2 2
5 8
6 1
8 7
输出示例
56
其他说明
对于40%的数据,1<=n<=7
对于100%的数据,1<=n<=1000 0<=D<=1000 0<=w<=1000

恩,我好像出过这道题,样例还一模一样,那就把std拿来好了。

人安慰过的MZ肯定是一段区间,设f[l][r][d]表示已经安慰了[l,r]现在在左侧还是在右侧的答案,转移时费用提前计算即可。

#include<iostream>
using namespace std;
const int maxn=1010;
const int INF=1000000000;
struct People
{
    int D,W,id;
    bool operator < (const People& ths) const
    {
         return D<ths.D;
    }
}people[maxn];
int f[maxn][maxn][2],v,S[maxn];
int main()
{
    int n;
    scanf("%d%d",&n,&v);
    for(int i=1;i<=n;i++)
    {
        scanf("%d%d",&people[i].D,&people[i].W);
        people[i].id=i;
    }
    sort(people+1,people+n+1);
    for(int i=1;i<=n;i++)
    {
        S[i]=S[i-1]+people[i].W;
    }
    int now;
    for(int i=1;i<=n;i++)
      for(int j=1;j<=n;j++)
        f[i][j][0]=f[i][j][1]=INF;
    for(int i=1;i<=n;i++) if(people[i].id==v) {now=i;break;}
    f[now][now][0]=f[now][now][1]=0;
    for(int L=1;L<=n;L++)
      for(int i=1;i+L-1<=n;i++)
      {
          int j=i+L-1;
          f[i-1][j][0]=min(f[i-1][j][0],min(f[i][j][1]+(people[j].D-people[i-1].D)*(S[n]-S[j]+S[i-1]),f[i][j][0]+(people[i].D-people[i-1].D)*(S[n]-S[j]+S[i-1])));
          f[i][j+1][1]=min(f[i][j+1][1],min(f[i][j][1]+(people[j+1].D-people[j].D)*(S[n]-S[j]+S[i-1]),f[i][j][0]+(people[j+1].D-people[i].D)*(S[n]-S[j]+S[i-1])));
      }
    printf("%d
",min(f[1][n][0],f[1][n][1]));
    return 0;
}
View Code
土豪聪要请客
难度级别:C; 运行时间限制:1000ms; 运行空间限制:51200KB; 代码长度限制:2000000B
试题描述
众所周知,聪哥(ndsf)是个土豪,不过你们不知道的是他的MZ和他的RMB一样滴多……
某天土豪聪又赚了10^10000e的RMB,他比较开心,于是准备请客。他在自己在XX星上的别墅里面大摆酒席,想要邀请尽可能多的MZ来参加他的宴会。他将会同MZ一起坐在一个巨大的长方形桌子上。这个桌子能坐下的人数等于他的边长。聪哥要求他的桌子能够放进他的别墅,并且桌子的边必须与别墅的边界平行。给定别墅的平面图,请你求出聪哥最多可以请多少个MZ。
输入
第一行n,m。表示别墅的长宽
下面n行,每行m个字符,表示一个方块是空的(‘.’)或是被占用了(‘X’)。
聪哥只要他的桌子放在别墅里,并且桌子不能占用任何一个已经占用了的方块。
输出
一个数,表示聪哥最多可以请几个Maze。
输入示例
2 2
..
..
输出示例
7
其他说明
对于60%的数据,n,m<=100
对于100%的数据,n,m<=400

第二版上有这道题。

从上到下,从左到右扫,扫描时用一个单调栈维护(c,h),表示高度为h的点向左最多到c。

如果我讲的不够清楚可以看第二版高效求解一章最后一道例题。

#include<cstdio>
#include<cctype>
#include<queue>
#include<cmath>
#include<cstring>
#include<algorithm>
#define rep(i,s,t) for(int i=s;i<=t;i++)
#define dwn(i,s,t) for(int i=s;i>=t;i--)
#define ren for(int i=first[x];i!=-1;i=next[i])
using namespace std;
inline int read() {
    int x=0,f=1;char c=getchar();
    for(;!isdigit(c);c=getchar()) if(c=='-') f=-1;
    for(;isdigit(c);c=getchar()) x=x*10+c-'0';
    return x*f;
}
const int maxn=410;
struct Rect {int c,h;}q[maxn];
int n,m,ans,h[maxn];
char Map[maxn][maxn];
int main() {
    n=read();m=read();
    rep(i,1,n) scanf("%s",Map[i]+1);
    rep(i,1,n) {
        int top=0;
        rep(j,1,n) {
            if(Map[i][j]!='.') h[j]=0;
            else h[j]++;
            if(h[j]) {
                Rect r=(Rect){j,h[j]};
                while(top&&q[top].h>=h[j]) r.c=q[top--].c;
                if(!top||r.h-r.c>q[top].h-q[top].c) q[++top]=r;
                ans=max(ans,(q[top].h+j-q[top].c+1)*2-1);
            }
            else top=0;
        }
    }
    printf("%d
",ans);
    return 0;
}
View Code
最强大脑
难度级别:C; 运行时间限制:20000ms; 运行空间限制:262144KB; 代码长度限制:2000000B
试题描述
zhb国是一个传说中的国度,国家的居民叫做最强(chang)大脑(diao)。Zhb国是一个N×M的矩形方阵,每个格子代表一个街区。
然而zhb国是没有交通工具的。居民大脑(diao)们完全靠地面的弹射装置来移动。
每个街区都装有弹射装置。使用弹射装置是需要支付一定费用的。而且每个弹射装置都有自己的弹射能力。

我们设第i行第j列的弹射装置有Aij的费用和Bij的弹射能力。并规定有相邻边的格子间距离是1。那么,任何居民大脑(diao)都只需要在(i,j)支付Aij的费用就可以任意选择弹到距离不超过Bij的位置了。如下图

(从红色街区交费以后可以跳到周围的任意蓝色街区。)
现在的问题很简单。有三个居民,她们分别是zhb的maze,分别叫做X,Y,Z。现在她们决定聚在一起玩找zhb玩(….),于是想往其中一人的位置集合。但由于zhb太抠门了,不给报销路费,所以告诉你3个maze的坐标,求往哪里集合大家需要花的费用总和最低。
Zhb:如果你完美解决了这个问题,就授予你“最强大脑”称号。

输入
输入的第一行包含两个整数N和M,分别表示行数和列数。
接下来是2个N×M的自然数矩阵,为Aij和Bij
最后一行六个数,分别代表X,Y,Z所在地的行号和列号。
输出
第一行输出一个字符X、Y或者Z。表示最优集合地点。
第二行输出一个整数,表示最小费用。
如果无法集合,只输出一行NO
输入示例
4 4
0 0 0 0
1 2 2 0
0 2 2 1
0 0 0 0
5 5 5 5
5 5 5 5
5 5 5 5
5 5 5 5
2 1 3 4 2 2
输出示例
Z
15
其他说明
20%  N, M ≤ 10;   Bij ≤ 20
40%  N, M ≤ 100;   Bij ≤ 20
100%  1 ≤ N, M ≤ 150;  0 ≤ Bij ≤ 109;  0 ≤ Aij ≤ 1000

标准做法是将能量计入状态减少连边数。

#include<cstdio>
#include<cctype>
#include<queue>
#include<cstring>
#include<algorithm>
#define rep(i,s,t) for(int i=s;i<=t;i++)
#define dwn(i,s,t) for(int i=s;i>=t;i--)
#define ren for(int i=first[x];i!=-1;i=next[i])
using namespace std;
inline int read() {
    int x=0,f=1;char c=getchar();
    for(;!isdigit(c);c=getchar()) if(c=='-') f=-1;
    for(;isdigit(c);c=getchar()) x=x*10+c-'0';
    return x*f;
}
const int maxn=155;
const int MAXN=maxn*maxn*300;
const int mx[]={1,-1,0,0,0};
const int my[]={0,0,1,-1,0};
int n,m,x1,y1,x2,y2,x3,y3,d[MAXN];
int A[maxn][maxn],B[maxn][maxn];
bool done[MAXN];
int id(int x,int y,int h) {
    return h*n*m+(x-1)*m+y;
}
void decode(int& x,int& y,int& h,int ID) {
    ID--;h=ID/(n*m);ID%=(n*m);
    x=ID/m+1;y=ID%m+1;
}
struct HeapNode {
    int d,u;
    bool operator < (const HeapNode& ths) const {
        return d>ths.d;
    }
};
int D[3][3];
priority_queue<HeapNode> Q;
void dijkstra(int X,int Y,int T) {
    while(!Q.empty()) Q.pop();
    memset(d,125,sizeof(d));
    memset(done,0,sizeof(done));
    int x=id(X,Y,0),ok=0;d[x]=0;Q.push((HeapNode){0,x});
    while(!Q.empty()) {
        x=Q.top().u;Q.pop();
        if(done[x]) continue;done[x]=1;
        int x0,y0,h;decode(x0,y0,h,x);
        if(x0==x2&&y0==y2&&!(ok&1)) {
            D[T][1]=d[x];
            ok++;
        }
        if(x0==x3&&y0==y3&&!(ok&2)) {
            D[T][2]=d[x];
            ok+=2;
        }
        if(x0==x1&&y0==y1&&!(ok&4)) {
            D[T][0]=d[x];
            ok+=4;
        }
        if(ok==7) return;
        if(!h) {
            int y=id(x0,y0,min(h+A[x0][y0],300));
            if(d[y]>d[x]+B[x0][y0]) {
                d[y]=d[x]+B[x0][y0];
                Q.push((HeapNode){d[y],y});
            }
        }
        else {
            rep(dir,0,4) {
                int nx=x0+mx[dir],ny=y0+my[dir],nh=h-1;
                if(nx<=0||nx>n||ny<=0||ny>m) continue;
                int y=id(nx,ny,nh);
                if(d[y]>d[x]) {
                    d[y]=d[x];
                    Q.push((HeapNode){d[y],y});
                }
            }
        }
    }
}
int main() {
    n=read();m=read();
    rep(i,1,n) rep(j,1,m) A[i][j]=read();
    rep(i,1,n) rep(j,1,m) B[i][j]=read();
    x1=read(),y1=read(),x2=read(),y2=read(),x3=read(),y3=read();
    rep(i,0,2) rep(j,0,2) D[i][j]=1<<29;
    dijkstra(x1,y1,0);dijkstra(x2,y2,1);dijkstra(x3,y3,2);
    int ans=1<<29,T;
    rep(i,0,2) {
        int res=0;
        rep(j,0,2) if(i!=j) res+=D[j][i];
        if(res<ans) ans=res,T=i;
    }
    if(ans==1<<29) puts("NO");
    else printf("%c
%d
",T+'X',ans);
    return 0;
}
View Code

然而自从建图嵌套数据结构这种方法诞生后就有了更优秀的解法。

可以将kd树嵌到建图中。

分火腿
难度级别:C; 运行时间限制:1000ms; 运行空间限制:51200KB; 代码长度限制:2000000B
试题描述

小月言要过四岁生日了,她的妈妈为她准备了n根火腿,她想将这些火腿均分给m位小朋友,所以她可能需要切火腿。为了省事,小月言想切最少的刀数,使这n根火腿分成均等的m份。请问最少要切几刀?

输入
第一行一个整数T,表示有T组数据。
接下来T组数据,每组共一行,有两个数字n,m。
输出
每组数据一行,输出最少要切的刀数。
输入示例
2
2 6
6 2
输出示例
4
0
其他说明
100%的数据保证T<=1000;n,m<=2147483647。

每有一个切点与断点重合,答案-1。

那么可以推出ans=m-gcd(n,m)

#include<cstdio>
#include<cctype>
#include<queue>
#include<cmath>
#include<cstring>
#include<algorithm>
#define rep(i,s,t) for(int i=s;i<=t;i++)
#define dwn(i,s,t) for(int i=s;i>=t;i--)
#define ren for(int i=first[x];i!=-1;i=next[i])
using namespace std;
inline int read() {
    int x=0,f=1;char c=getchar();
    for(;!isdigit(c);c=getchar()) if(c=='-') f=-1;
    for(;isdigit(c);c=getchar()) x=x*10+c-'0';
    return x*f;
}
int gcd(int a,int b) {return !b?a:gcd(b,a%b);}
int main() {
    dwn(i,read(),1) {
        int n=read(),m=read();
        printf("%d
",max(m-gcd(n,m),0));
    }
    return 0;
}
View Code
班服
难度级别:C; 运行时间限制:1000ms; 运行空间限制:51200KB; 代码长度限制:2000000B
试题描述

要开运动会了,神犇学校的n个班级要选班服,班服共有100种样式,编号1~100。现在每个班都挑出了一些样式待选,每个班最多有100个待选的样式。要求每个班最终选定一种样式作为班服,且该班的样式不能与其他班级的相同,求所有可能方案的总数,由于方案总数可能很大,所以要求输出mod 1000000007后的答案。

输入
共有T组数据。
对于每组数据,第一行为一个整数n,表示有n个班级。
2~n+1行,每行有最多100个数字,表示第i-1班待选班服的编号。
输出
对于每组数据,输出方案总数 mod 1000000007后的答案。
输入示例
2
3
5 100 1
2
5 100
2
3 5
8 100
输出示例
4
4
其他说明
对于30%的数据,1<=T<=3, 1<=n<=3,每班待选样式不超过10种。
对于50%的数据,1<=T<=5, 1<=n<=5,每班待选样式不超过50种。
对于100%的数据,1<=T<=10, 1<=n<=10,每班待选样式不超过100种。

状压DP好了。f[i][S]表示前i种样式集合S的班级已经选了的方案数。

#include<cstdio>
#include<cctype>
#include<queue>
#include<cmath>
#include<cstring>
#include<algorithm>
#define rep(i,s,t) for(int i=s;i<=t;i++)
#define dwn(i,s,t) for(int i=s;i>=t;i--)
#define ren for(int i=first[x];i!=-1;i=next[i])
using namespace std;
int yes;
inline int read() {
    int x=0,f=1;char c=getchar();
    for(;!isdigit(c);c=getchar()) if(c=='-') f=-1;
    for(;isdigit(c);c=getchar()) x=x*10+c-'0';
    if(c=='
') yes=1;
    return x*f;
}
const int mod=1000000007;
int A[110],f[110][1050];
int main() {
    f[0][0]=1;
    dwn(T,read(),1) {
        memset(A,0,sizeof(A));
        int n=read();
        rep(i,1,n) {
            yes=0;
            while(!yes) A[read()]|=1<<i-1;
        }
        rep(i,1,100) rep(S,0,(1<<n)-1) {
            f[i][S]=f[i-1][S];
            rep(j,0,n-1) if((A[i]>>j&1)&&(S>>j&1)) (f[i][S]+=f[i-1][S^(1<<j)])%=mod;
        }
        printf("%d
",f[100][(1<<n)-1]);
    }
    return 0;
}
View Code
时间与空间之旅
难度级别:C; 运行时间限制:1000ms; 运行空间限制:51200KB; 代码长度限制:2000000B
试题描述
公元22××年,宇宙中最普遍的交通工具是spaceship。spaceship的出现使得星系之间的联系变得更为紧密,所以spaceship船长也成了最热门的职业之一。当然,要成为一名出色的船长,必须通过严格的考核,例如下面是最简单的问题中的一个。
用1~n的整数给n个星系标号,目前你在标号为1的星系,你需要送快递到标号为n的星系,星系之间由于存在陨石带,并不是都可以直连的。同时,由于超时空隧道的存在,在某些星系间飞行会出现时间静止甚至倒流,飞行时间为0或为负数。另外,由星系i到星系j的时间和由星系j到星系i的时间不一定是相同的。

在寄出日期之前收到快递被认为是不允许的,所以每部spaceship上都有一个速度调节装置,可以调节飞行的时间。简单来说其功能就是让所有两个星系间的飞行时间(如果可以直达)都增加或减少相同的整数值,你的任务就是调整速度调节器,找出一条用最短时间完成任务的路径,并且保证这个最短时间的值大于或等于0。

输入
输入文件包含多组数据,第1个数为T,表示数据的数量。
对于每一组数据,输入第1行为两个正整数N(2≤N≤100),E(1≤E≤N*(N-1)/2),为星系的个数和星系间飞行的路线数。然后E行,每行三个整数i,j和t(1≤i,j≤N,i≠j,-100000≤t≤100000),表示由星系i到星系j飞行的时间为t。由i到j最多只会有一条飞行线路。
输出
输出文件共T行,每组数据输出一行;
如果可以通过调节速度调节器完成任务,则输出一个非负整数,表示由星系1到星系N的最短时间。
如果不能由星系1到达星系N,则输出-1。
输入示例
1
4 5
1 2 1
1 3 1
2 3 -3
3 1 1
3 4 1
输出示例
2
其他说明
输入样例如图所示,其中节点标号表示相应星系,节点间数字表示所需时间。
如果设置速度控制器的值为0,则有如下路径:1→2→3→1→2→……→3→4,使得投递的时间为负无穷大,显然是不符合要求的,所以应该把速度控制器的值设为1,相当于每个时间值加1,得到的最短路径为1→2→3→4,所需时间为2+(-2)+2=2。

将不可能在路线中的点去掉,二分+SPFA判定即可。

不可能在路线中的点是从S出发不能到达或不能到达T的点。

#include<cstdio>
#include<cctype>
#include<queue>
#include<cmath>
#include<cstring>
#include<algorithm>
#define rep(i,s,t) for(int i=s;i<=t;i++)
#define dwn(i,s,t) for(int i=s;i>=t;i--)
#define ren for(int i=first[x];i;i=next[i])
using namespace std;
inline int read() {
    int x=0,f=1;char c=getchar();
    for(;!isdigit(c);c=getchar()) if(c=='-') f=-1;
    for(;isdigit(c);c=getchar()) x=x*10+c-'0';
    return x*f;
}
const int maxn=110;
const int maxm=10010;
const int INF=1000000000;
struct SPFA {
    queue<int> Q;
    int n,m,bid[maxn],first[maxn],next[maxm];
    int d[maxn],inq[maxn],cnt[maxn];
    struct Edge {int from,to,dist;}edges[maxm];
    void init(int n) {
        this->n=n;m=0;
        memset(first,0,sizeof(first));
        memset(bid,0,sizeof(bid));
    }
    void AddEdge(int w,int v,int u) {
        edges[++m]=(Edge){u,v,w};next[m]=first[u];first[u]=m;
    }
    int solve() {
        rep(i,1,n) d[i]=INF,cnt[i]=0,inq[i]=0;
        while(!Q.empty()) Q.pop();Q.push(1);d[1]=0;
        while(!Q.empty()) {
            int x=Q.front();Q.pop();inq[x]=0;
            ren {
                Edge& e=edges[i];
                if(!bid[e.to]&&d[e.to]>d[x]+e.dist) {
                    d[e.to]=d[x]+e.dist;
                    if(!inq[e.to]) {
                        inq[e.to]=1,Q.push(e.to);
                        if(++cnt[e.to]>n) return 0;
                    }
                }
            }
        }
        return d[n]>=0&&d[n]!=INF;
    }
    void dfs(int x) {
        inq[x]=1;
        ren if(!inq[edges[i].to]) dfs(edges[i].to);
    }
    void get_rid_of_useless_point() {
        memset(inq,0,sizeof(inq));dfs(1);
        rep(i,1,n) if(!inq[i]) bid[i]=1;
        rep(i,1,n) if(!bid[i]) {
            memset(inq,0,sizeof(inq));dfs(i);
            if(!inq[n]) bid[i]=1;
        }
    }
}sol;
int check(int x) {
    rep(i,1,sol.m) sol.edges[i].dist+=x;
    int res=sol.solve();
    rep(i,1,sol.m) sol.edges[i].dist-=x;
    return res;
}
int main() {
    int T=read();
    while(T--) {
        sol.init(read());
        dwn(i,read(),1) sol.AddEdge(read(),read(),read());sol.get_rid_of_useless_point();
        int l=0,r=200000;
        if(!check(r)) puts("-1");
        else {
            while(l<r) {
                int mid=l+r>>1;
                if(check(mid-100000)) r=mid; 
                else l=mid+1;
            }
            check(l-100000);printf("%d
",sol.d[sol.n]);
        }
    }
    return 0;
}
View Code
狐狸的谜语
难度级别:C; 运行时间限制:1000ms; 运行空间限制:51200KB; 代码长度限制:2000000B
试题描述
话说某一个月黑风高的晚上,一只褐色的狐狸快速地跳过了一只懒狗,并留下一个字符串“032089”和一个数字5。
这其中一定隐含了某些秘密!酷爱思考的你马上发现,这个字符串可以写成:“03+2+0*89”,结果为5。这是一个非常有趣的问题!
现在给出一个长度为N的数字字符串和一个数字T,要求插入最少的加号或者乘号,使得数字字符串的运算结果为T。运算符*号优先级高于+号,运算数可以有任意个前导0。
输入
输入不超过5组数据,每组数据两行。
每组数据的第1行为长度为N,只包含0~9的数字字符串,第2行为一个数字T。
输入T<0表示输入结束。
输出
输出一个数字单独占一行,表示最少需要添加的运算符(+号或*号)数,无解输出-1。
输入示例
032089
5
333
9
00
-1
输出示例
3
2
其他说明
对于30%的数据,有1≤N≤10,0≤T≤50。
对于50%的数据,有1≤N≤15,0≤T≤200。
对于全部的数据,有1≤N≤20,0≤T≤200。

由于不能填括号,设g[l][r][v]表示[l,r]通过填*号能否得到v,f[l][r][v]表示[l,r]能否通过填+号和*号得到v。

细节挺多的。

#include<cstdio>
#include<cctype>
#include<queue>
#include<cmath>
#include<cstring>
#include<algorithm>
#define rep(i,s,t) for(int i=s;i<=t;i++)
#define dwn(i,s,t) for(int i=s;i>=t;i--)
#define ren for(int i=first[x];i!=-1;i=next[i])
using namespace std;
inline int read() {
    int x=0,f=1;char c=getchar();
    for(;!isdigit(c);c=getchar()) if(c=='-') f=-1;
    for(;isdigit(c);c=getchar()) x=x*10+c-'0';
    return x*f;
}
const int maxn=21;
const int maxv=210;
char s[maxn];
int v,f[maxn][maxn][maxv],g[maxn][maxn][maxv];
void update(int& ans,int v) {
    if(v<0) return;
    if(ans<0) ans=v;
    else ans=min(ans,v);
}
int main() {
    while(scanf("%s",s+1)) {
        memset(f,-1,sizeof(f));
        memset(g,-1,sizeof(g));
        v=read();if(v<0) break;
        int n=strlen(s+1);
        rep(l,1,n) rep(r,l,n) {
            int res=0;
            rep(i,l,r) {
                res=res*10+s[i]-'0';
                if(res>v) break;
            }
            if(res<=v) f[l][r][res]=g[l][r][res]=0;
        }
        dwn(l,n,1) rep(r,l,n) {
            rep(k,l,r-1) { //[l,k] and [k+1,r]    
                if(g[l][k][0]>=0) update(g[l][r][0],g[l][k][0]+1);
                if(g[k+1][r][0]>=0) update(g[l][r][0],g[k+1][r][0]+1);
                rep(v1,0,v) rep(v2,0,v) if(v1*v2<=v&&g[l][k][v1]>=0&&g[k+1][r][v2]>=0)
                    update(g[l][r][v1*v2],g[l][k][v1]+g[k+1][r][v2]+1);
                rep(v1,0,v) rep(v2,0,v-v1) {
                    if(f[l][k][v1]>=0&&f[k+1][r][v2]>=0)
                    update(f[l][r][v1+v2],f[l][k][v1]+f[k+1][r][v2]+1);
                }
            }
            rep(i,0,v) update(f[l][r][i],g[l][r][i]);
        }
        printf("%d
",f[1][n][v]);
    }
    return 0;
}
View Code

然而看到黄学长毅然暴力踩标程,吓傻了。

#include<iostream>
#include<cstring>
#include<cstdio>
#include<cstdlib>
#include<cmath>
#include<algorithm>
#define ll long long
using namespace std;
int read()
{
    int x=0,f=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
    return x*f;
}
char a[25];
int len,t,deep,v[25][25];
bool flag,b[25];
void pre()
{
    for(int i=len;i;i--)
        if(a[i]=='0')
        {
            while(i--)b[i]=0;
            break;
        }
        else b[i]=1;
    for(int i=1;i<=len;i++)
        for(int j=i;j<=len;j++)
        {
            v[i][j]=v[i][j-1]*10+a[j]-'0';
            v[i][j]=min(v[i][j],t+1);
        }
}
void dfs(int x,int k,int last,int mul,int sum)
{
    if(sum>t||flag||k>deep)return;
    if(x==len)
    {
        if(sum+mul*v[last+1][len]==t)flag=1;
        else return;
    }
    if(b[x-1]&&sum+mul>t)return;
    dfs(x+1,k+1,x,1,sum+mul*(v[last+1][x]));
    dfs(x+1,k+1,x,mul*(v[last+1][x]),sum);
    dfs(x+1,k,last,mul,sum);
}
void solve()
{
    pre();
    int ans=-1;
    int l=0,r=len-1;
    while(l<=r)
    {
        deep=(l+r)>>1;
        flag=0;
        dfs(1,0,0,1,0);
        if(flag)ans=deep,r=deep-1;
        else l=deep+1;
    }
    printf("%d
",ans);
}
int main()
{
    while(scanf("%s%d",a+1,&t))
    {
        if(t==-1)return 0;
        len=strlen(a+1);
        solve();
    }
    return 0;
}
View Code
Graph
难度级别:C; 运行时间限制:1000ms; 运行空间限制:51200KB; 代码长度限制:2000000B
试题描述

给出N个点,M条边的有向图,对于每个点v,求A(v)表示从点v出发,能到达的编号最大的点。

输入
第1行,2个整数N,M。接下来M行,每行2个整数Ui,Vi,表示边Ui,Vi。点用1,2,...,N编号。
输出
N个整数A(1),A(2),...,A(N)。
输入示例
4 3
1 2
2 4
4 3
输出示例
4 4 3 4
其他说明
对于 60% 的数据,1 ≤ N,K ≤ 10^3
对于 100% 的数据,1 ≤ N,M ≤ 10^5。

反向连边BFS。

#include<cstdio>
#include<cctype>
#include<queue>
#include<cmath>
#include<cstring>
#include<algorithm>
#define rep(i,s,t) for(int i=s;i<=t;i++)
#define dwn(i,s,t) for(int i=s;i>=t;i--)
#define ren for(int i=first[x];i;i=next[i])
using namespace std;
inline int read() {
    int x=0,f=1;char c=getchar();
    for(;!isdigit(c);c=getchar()) if(c=='-') f=-1;
    for(;isdigit(c);c=getchar()) x=x*10+c-'0';
    return x*f;
}
const int maxn=100010;
int n,m,vis[maxn],first[maxn],next[maxn],to[maxn],e;
void AddEdge(int u,int v) {
    to[++e]=v;next[e]=first[u];first[u]=e;
}
void dfs(int x,int cur) {
    vis[x]=cur;
    ren if(!vis[to[i]]) dfs(to[i],cur);
}
int main() {
    n=read();m=read();
    rep(i,1,m) AddEdge(read(),read());
    dwn(i,n,1) if(!vis[i]) dfs(i,i);
    printf("%d",vis[1]);rep(i,2,n) printf(" %d",vis[i]);
    return 0;
}
View Code
Incr
难度级别:C; 运行时间限制:1000ms; 运行空间限制:262144KB; 代码长度限制:2000000B
试题描述

数列 A1,A2,...,AN,将最少的数字修改成其它整数,使得数列严格单调递增。

输入
第 1 行,1 个整数 N 
第 2 行,N 个整数 A1,A2,...,AN
输出
1 个整数,表示最少修改的数字
输入示例
3
1 3 2
输出示例
1
其他说明
对于 50% 的数据,N ≤ 10^3 
对于 100% 的数据,1 ≤ N ≤ 10^5,1 ≤ Ai ≤ 10^9

可以选出一些位置不动,将其它位置全部修改。

那么一个位置可以不动的必要条件是什么呢?

设x为一个已经决定不动的位置,y为需要判定是否可以不动的位置。则x、y之间还要填y-x-1个数,则Ay-Ax>y-x-1,Ay-y>=Ax-x

将Ai变为Ai-i,做最长不降子序列即可。

#include<cstdio>
#include<cctype>
#include<queue>
#include<cmath>
#include<cstring>
#include<algorithm>
#define rep(i,s,t) for(int i=s;i<=t;i++)
#define dwn(i,s,t) for(int i=s;i>=t;i--)
#define ren for(int i=first[x];i!=-1;i=next[i])
using namespace std;
inline int read() {
    int x=0,f=1;char c=getchar();
    for(;!isdigit(c);c=getchar()) if(c=='-') f=-1;
    for(;isdigit(c);c=getchar()) x=x*10+c-'0';
    return x*f;
}
const int maxn=100010;
int n,ans,A[maxn],g[maxn];
int main() {
    n=read();
    rep(i,1,n) A[i]=read()-i,g[i]=1<<30;
    rep(i,1,n) {
        int k=upper_bound(g+1,g+n+1,A[i])-g;
        ans=max(ans,k);g[k]=A[i];
    }
    printf("%d
",n-ans);
    return 0;
}
View Code
Permutation
难度级别:C; 运行时间限制:1000ms; 运行空间限制:51200KB; 代码长度限制:2000000B
试题描述
将 1 到 N 任意排列,然后在排列的每两个数之间根据他们的大小关系插入“>”和“<”。
问在所有排列中,有多少个排列恰好有K个“<”。 
例如排列(3, 4, 1, 5, 2) 
3 < 4 > 1 < 5 > 2 
共有2个“<”
输入
N,K
输出
答案
输入示例
5 2
输出示例
66
其他说明
20%:N <= 10 
50%:答案在0..2^63-1内 
100%:K < N <= 100

只是要写高精度了。。。

#include<cstdio>
#include<cctype>
#include<queue>
#include<cmath>
#include<cstring>
#include<algorithm>
#define rep(i,s,t) for(int i=s;i<=t;i++)
#define dwn(i,s,t) for(int i=s;i>=t;i--)
#define ren for(int i=first[x];i!=-1;i=next[i])
using namespace std;
inline int read() {
    int x=0,f=1;char c=getchar();
    for(;!isdigit(c);c=getchar()) if(c=='-') f=-1;
    for(;isdigit(c);c=getchar()) x=x*10+c-'0';
    return x*f;
}
const int maxn=110;
const int maxlen=170;
struct bign {
    int len,s[maxlen];
    bign() {len=1;memset(s,0,sizeof(s));}
    void clean() {
        while(len>1&&!s[len-1]) len--;
    }
    bign operator * (const int b) const {
        bign ans;ans.len=len+5;
        rep(i,0,ans.len-1) ans.s[i]=s[i]*b;
        rep(i,0,ans.len-2) ans.s[i+1]+=ans.s[i]/10,ans.s[i]%=10;
        ans.clean();return ans;
    }
    void operator += (const bign& b) {
        len=max(len,b.len)+1;
        rep(i,0,len-1) s[i]+=b.s[i];
        rep(i,0,len-2) s[i+1]+=s[i]/10,s[i]%=10;
        clean();
    }
    void print() {
        dwn(i,len-1,0) putchar(s[i]+'0');
        putchar('
');
    }
};
bign f[maxn][maxn];
int main() {
    int n=read(),k=read();
    f[1][0].s[0]=1;
    rep(i,1,n-1) rep(j,0,i-1) {
        f[i+1][j]+=f[i][j]*(j+1);
        f[i+1][j+1]+=f[i][j]*(i-j);
    }
    f[n][k].print();
    return 0;
}
View Code
三角形
难度级别:C; 运行时间限制:1000ms; 运行空间限制:51200KB; 代码长度限制:2000000B
试题描述
小J是一名文化课选手,他十分喜欢做题,尤其是裸题。下面就送大家一道裸题。给定一张N*M的网格,计算三个点都在网格上的三角形有多少个。三角形三点不能共线。如图是样例的网格以及上面的一个合法三角形。

 
输入
一行两个正整数N,M。
输出
一行一个整数代表数量。
输入示例
1 1
输出示例
4
其他说明
对于10%的数据有N,M<=10
对于30%的数据有N,M<= 100
对于100%的数据有N,M <= 1000

先n=n+1,m=m+1。

容斥原理。答案=总三角形数-三点共线的三角形数。

前者=(n*m)*(n*m-1)*(n*m-2)/6

后者可以这么算:一种是三点都在水平或竖直线上,答案=(m*n*(n-1)*(n-2)/6)+(n*m*(m-1)*(m-2)/6)

一种是斜着的,可以枚举矩形的尺寸,发现一个尺寸为r*c的矩形在图中出现了(n-r)*(n-c)次,固定让两点在矩阵的顶点,第三个点有gcd(r,c)-1个位置可待,因为是对称的,这部分答案要乘2

真是一道数学题。。。。

#include<cstdio>
#include<cctype>
#include<queue>
#include<cmath>
#include<cstring>
#include<algorithm>
#define rep(i,s,t) for(int i=s;i<=t;i++)
#define dwn(i,s,t) for(int i=s;i>=t;i--)
#define ren for(int i=first[x];i!=-1;i=next[i])
using namespace std;
inline int read() {
    int x=0,f=1;char c=getchar();
    for(;!isdigit(c);c=getchar()) if(c=='-') f=-1;
    for(;isdigit(c);c=getchar()) x=x*10+c-'0';
    return x*f;
}
typedef long long ll;
int gcd(int a,int b) {return !b?a:gcd(b,a%b);}
int main() {
    ll n=read()+1,m=read()+1,ans=0;
    ans+=(ll)(n*m)*(n*m-1)*(n*m-2)/6;
    ans-=(ll)(m*n*(n-1)*(n-2)/6)+(ll)(n*m*(m-1)*(m-2)/6);
    rep(i,1,n-1) rep(j,1,m-1) {
        ll c=gcd(i,j)+1;
        if(c>2) ans-=(ll)2*(c-2)*(n-i)*(m-j);
    }
    printf("%lld
",ans);
    return 0;
}
View Code
忧桑三角形
难度级别:C; 运行时间限制:1000ms; 运行空间限制:51200KB; 代码长度限制:2000000B
试题描述

小J是一名文化课选手,他十分喜欢做题,尤其是裸题。有一棵树,树上每个点都有点权,现在有以下两个操作:

1. 修改某个点的点权

2. 查询点u和点v构成的简单路径上是否能选出三个点组成三角形

输入
第一行两个整数N,Q,代表点数和询问数。第二行N个整数表示点权。下面N-1行每行两个整数a,b代表a,b之间有一条边。下面Q行每行3个整数t,a,b:若t=0,询问(a,b);否则将点a的权值修改为b
输出
对于每个询问输出Y表示能构成三角形,输出N表示不能构成三角形
输入示例
5 5
1 2 3 4 5
1 2
2 3
3 4
1 5
0 1 3
0 4 5
1 1 4
0 2 5
0 2 3
输出示例
N
Y
Y
N
其他说明
对于30%的数据有N,Q<= 1000
对于100%的数据有N,Q<= 10^5,点权范围在32位整数范围内

直觉告诉我们,当两点之间距离很远的话肯定能构出三角形。

让我们站在出题人的立场上,考虑怎么构造数据才能构出反例。
设s到t依次经过若干边:1、1、3、5、9、15……

很像Fib数列,增长速度很快。写个程序发现第四十多项就爆int了。

好,我们就有算法了。如果两点距离>=50,直接输出“Yes”。否则排序暴力判。

#include<cstdio>
#include<cctype>
#include<queue>
#include<cmath>
#include<cstring>
#include<algorithm>
#define rep(i,s,t) for(int i=s;i<=t;i++)
#define dwn(i,s,t) for(int i=s;i>=t;i--)
#define ren for(int i=first[x];i;i=next[i])
using namespace std;
inline int read() {
    int x=0,f=1;char c=getchar();
    for(;!isdigit(c);c=getchar()) if(c=='-') f=-1;
    for(;isdigit(c);c=getchar()) x=x*10+c-'0';
    return x*f;
}
const int maxn=100010;
int n,m,fa[maxn],anc[maxn][20],dep[maxn],val[maxn],first[maxn],next[maxn<<1],to[maxn<<1],e;
void AddEdge(int u,int v) {
     to[++e]=v;next[e]=first[u];first[u]=e;
     to[++e]=u;next[e]=first[v];first[v]=e;
}
void dfs(int x) {
    dep[x]=dep[fa[x]]+1;anc[x][0]=fa[x];
    rep(i,1,19) anc[x][i]=anc[anc[x][i-1]][i-1];
    ren if(to[i]!=fa[x]) fa[to[i]]=x,dfs(to[i]);
}
int query(int x,int y) {
    if(dep[x]<dep[y]) swap(x,y);
    dwn(i,19,0) if(1<<i<=dep[x]-dep[y]) x=anc[x][i];
    dwn(i,19,0) if(anc[x][i]!=anc[y][i]) x=anc[x][i],y=anc[y][i];
    return x==y?x:fa[x]; 
}
long long A[maxn];
int main() {
    n=read();m=read();
    rep(i,1,n) val[i]=read();
    rep(i,2,n) AddEdge(read(),read());
    dfs(n/2);
    while(m--) {
        if(!read()) {
            int x=read(),y=read(),c=query(x,y);
            if(dep[x]+dep[y]-2*dep[c]>=50) puts("Y");
            else {
                 int cnt=0,ok=0;
                 while(x!=c) A[++cnt]=val[x],x=fa[x];
                 while(y!=c) A[++cnt]=val[y],y=fa[y];
                 A[++cnt]=val[c];sort(A+1,A+cnt+1);
                 if(cnt<3) puts("N");
                 else {
                      rep(i,3,cnt) if(A[i-2]+A[i-1]>A[i]) {ok=1;break;} 
                      puts(ok?"Y":"N");    
                 }
            }
        }   
        else {
            int x=read(),v=read();
            val[x]=v;     
        }        
    }
    return 0;    
}
View Code
难度级别:C; 运行时间限制:1000ms; 运行空间限制:51200KB; 代码长度限制:2000000B
试题描述

给你一个长度为n的序列A,请你求出一对Ai,Aj(1<=i<j<=n)使Ai“与”Aj最大。

Ps:“与”表示位运算and,在c++中表示为&。

输入
第一行为n。接下来n行,一行一个数字表示Ai。
输出
输出最大的Ai“与”Aj的结果。
输入示例
3
8
10
2
输出示例
8
其他说明
样例解释:
8 and 10 = 8
8 and 2 = 0
10 and 2 = 2
数据范围:
20%的数据保证n<=5000
100%的数据保证 n<=3*10^5,0<=Ai<=10^9

按位确定答案,优先填1。

#include<cstdio>
#include<cctype>
#include<queue>
#include<cmath>
#include<cstring>
#include<algorithm>
#define rep(i,s,t) for(int i=s;i<=t;i++)
#define dwn(i,s,t) for(int i=s;i>=t;i--)
#define ren for(int i=first[x];i;i=next[i])
using namespace std;
inline int read() {
    int x=0,f=1;char c=getchar();
    for(;!isdigit(c);c=getchar()) if(c=='-') f=-1;
    for(;isdigit(c);c=getchar()) x=x*10+c-'0';
    return x*f;
}
const int maxn=300010;
int n,ans,A[maxn],is[maxn];
int main() {
    n=read();
    rep(i,1,n) A[i]=read(),is[i]=1;
    dwn(j,31,0) {
        int cnt=0;
        rep(i,1,n) if(is[i]&&A[i]>>j&1) cnt++;
        if(cnt>=2) {
            ans|=1<<j;
            rep(i,1,n) if(is[i]&&!(A[i]>>j&1)) is[i]=0;           
        }       
    }
    printf("%d
",ans);
    return 0;    
}
View Code
原文地址:https://www.cnblogs.com/wzj-is-a-juruo/p/4936539.html