大连海事大学2017ACM程序设计大赛题解

大连海事大学2017ACM程序设计大赛题解

A

题目分析:

本题题干较长,信息量很大,但是算法并不复杂,是一道较复杂的模拟,建议做题时可以先跳过。

1.题中规定闰年的计算方法(闰年二月为29天)

当本年在1582年之前时,若年份为四的倍数则为闰年否则为平年。

若本年年份大于等于1582年,则年份为四的倍数且不为100的倍数则为闰年,当年份为400的倍数时也为闰年。

2.特殊的:1752年的9月3号到13号都没有天数

3.一个月是good的充要条件是:该月的第一个工作日为星期一,也就是说该月第一天是星期六或星期天或星期一。对于1752年9月来讲因为其没有9月3号,所以要想是good则第一天必须是星期天或者星期一。

一个月是lucky的充要条件是:该月的最后一个工作日是星期五,也就是说该月的最后一天为星期六或星期天或星期五。
4.1年1月1日为星期六

接下来代码思路就是先找到起始年起始月第一天是星期几然后一个月一个月地模拟即可。

函数Is_leapyear用来判断该年是否为闰年。

函数cal用来得出起始年起始月第一天是星期几。

代码:

#include<stdio.h>  
#include<stdlib.h>  
#include<string.h>  
int sy,sm,ey,em,c;   
int lucky,good; 
int mon[13]={0,31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; //用来保存平年每个月的天数
bool Is_leapyear(int year){ //判断是否为闰年
    if(year<1582){  
        if(year%4==0)  
            return true;  
        return false;  
    }  
    else{  
        if(year%4==0 && year%100!=0 || year%400==0 || year==1700)  
            return true;  
        else  
            return false;  
    }  
}         
int cal(int year,int month){ 
    int day=0;  
    for(int i=1;i<year;i++){ 
        if(Is_leapyear(i))
            day+=366;  
        else  
            day+=365;  
    }  
    for(int i=1;i<month;i++) //累加起始月之前的所有天数  
        if(i==2 && Is_leapyear(year)) //若为闰年二月则特殊处理  
            day+=(mon[i]+1);  
        else  
            day+=mon[i];  
    if(year>1752 || year==1752 && month>=10) // 
        day-=11;  //至此已经计算起始日期距离1年1月1日的天数  
    day=day%7+6; //计算起始日期为星期几  
    if(day!=7)  
        return day%7;  
    else  
        return day;  
}  
int main(){  
    scanf("%d",&c);  //c组数据
    while(c--){       
        scanf("%d%d%d%d",&sy,&sm,&ey,&em); //读入数据 
        int first=cal(sy,sm),last,year=sy,month=sm; //计算起始日期是星期几  
        //printf("%d\n",first);  
        lucky=good=0; //分别保存lucky月和good月的天数
        while(true){   
            if(year==ey && month>em || year>ey) //判断是否到了结束日期 若到则退出  
                break;  
            if(month==9 && year==1752){ //1752年9月特别处理  
                if(first==7 || first==1) //若该月第一天为星期天或者星期一则为good  
                    good++;  
                last=18%7+first; //last用来算出该月最后一天是星期几 
            }     
            else{ //普通情况
                if(first==7 || first==1 || first==6)  //判断是否为good月
                    good++;  
                if(Is_leapyear(year) && month==2)  //计算该月最后一天  
                    last=mon[month]%7+first;  
                else  
                    last=(mon[month]-1)%7+first;  
            }  
            if(last!=7) last%=7; //计算最后一天是星期几
            if(last==6 || last==7 || last==5) //判断是否为lucky月份  
                    lucky++;  
            first=last%7+1; // 计算下一个月的第一天是星期几  
            if(month==12){ //若为最后一个月,则跳到下一年一月  
                month=1;  
                year++;  
            }  
            else  
                month++;  
        }  
        printf("%d %d\n",lucky,good); //输出
    }  
    return 0;  
}

B

题目分析:
求sinx/x 在区间(a,b)之间的积分。由于a,b为1-10的整数,输出的为answer*10000取整,即结果保留精度到小数点后4位。

由于sinx/x的不定积分无法直接得到,根据精度用微分求面积解题,则精度保留在1e-5即可。时间复杂度为O(T*(b-a)*1e5)。

同时由于a,b范围很小,本题亦可以打表。
代码:
#include<bits/stdc++.h>
using namespace std;
/*int f[11][11]=
{
    0,0,0,0,0,0,0,0,0,0,0,
    0,0,6593,9025,8121,6038,4786,5085,6281,7189,7122,
    0,0,0,2432,1527,-554,-1807,-1508,-312,596,529,
    0,0,0,0,-904,-2987,-4239,-3940,-2744,-1836,-1903,
    0,0,0,0,0,-2082,-3335,-3036,-1840,-931,-998,
    0,0,0,0,0,0,-1252,-953,242,1151,1084,
    0,0,0,0,0,0,0,299,1494,2403,2336,
    0,0,0,0,0,0,0,0,1195,2104,2037,
    0,0,0,0,0,0,0,0,0,908,841,
    0,0,0,0,0,0,0,0,0,0,-66,
    0,0,0,0,0,0,0,0,0,0,0
};
int main()
{
    int a,b;
    while(scanf("%d%d",&a,&b)!=EOF)
        printf("%d\n",f[a][b]);
    return 0;
}*/
int main()
{
    int a,b;
    while(scanf("%d%d",&a,&b)!=EOF)
    {
        double ans = 0;
        for(double i=a;i<b;i+=1e-5)
        {
            ans+=sin(i)/i*1e-5;
        }
        printf("%d\n",(int)(ans*10000));
    }
    return 0;

C

题目分析:
给n个茶杯,每个茶杯容量问ai毫升,茶壶里共有w毫升茶,给定w <= sigma(ai) ,要将茶壶里的所有茶倒入杯中,设倒入第i个杯子的茶为bi,要满足:

    1.每个杯子里的茶量不小于容量的一半。

    2.每个杯子里的茶量为整毫升。

    3.如果容量ai > aj,则要求bi >= bj。

按顺序输出满足题意的每个杯子里茶量,若无法满足题意,输出-1。

一道简单的贪心题。

由题意得,输出-1的情况为无法满足要求1,只需要计数判断即可。

对于满足题意的情况,要满要求3,可以先将所有茶杯倒入刚好不小于ai一半的茶,剩余茶向容量最大茶杯开始依次倒。
代码:
#include<bits/stdc++.h>
using namespace std;

const int maxn = 105;
struct node
{
    int p,num,now;
}a[maxn];

bool cmp1(node &a,node &b)
{
    return a.num>b.num;
}
bool cmp2(node &a,node &b)
{
    return a.p<b.p;
}
void init()
{
    for(int i=0;i<maxn;i++)
        a[i].p=i;
}
int main()
{
    int n,w;
    scanf("%d%d",&n,&w);
    init();
    int ans = 0,xx=0;
    for(int i=0;i<n;i++)
    {
        int temp;
        scanf("%d",&temp);
        a[i].num=temp,xx+=temp;
        a[i].now=(temp+1)/2;
        ans += a[i].now;
    }
    if(ans>w){printf("-1\n");return 0;}
    w -=ans;
    sort(a,a+n,cmp1);
    for(int i=0;i<n;i++)
    {
        if(w<=0)break;
        int point = min(a[i].num-a[i].now,w);
        a[i].now+=point;
        w-=point;
    }
    sort(a,a+n,cmp2);
    for(int i=0;i<n-1;i++)
        printf("%d ",a[i].now);
    printf("%d\n",a[n-1].now);
    return 0;
}

D

题目分析:
定义poorness为一个区间内,所有数的和的绝对值。

求一个值x,将序列中所有元素-x,能使所得序列所有子区间的绝对值的最大值最小。

用三分查找区间,本题精度卡的有点严。long double + 3e-12过了。
代码:
#include<bits/stdc++.h>
using namespace std;

const long double eps = 3*1e-12;
const int maxn = 200000+10 ;
double a[maxn];
long double b[maxn];
int n;

long double getans()
{
    long double res = 0.0,ans = -1.0;
    for(int i=0;i<n;i++)
    {
        res+=b[i];
        if(res<0) res = 0;
        ans = max(ans,res);
    }
    return ans;
}
long double getres(long double x)
{
    long double ans = -1.0;
    for(int i=0;i<n;i++) b[i]=a[i]-x;
    ans = getans();
    for(int i=0;i<n;i++) b[i]*=-1.0;
    ans = max(ans,getans());
    return ans;
}
int main(){
    scanf("%d",&n);
    for (int i=0;i<n;i++) scanf("%lf",&a[i]);
    long double l=-10000.0,r=10000.0,mid1,mid2,res1=-1,res2=-1;
    while (l+eps<=r){
        mid1=(l+r)/2,mid2=(mid1+r)/2;
        res1=getres(mid1),res2=getres(mid2);
        if (res1>res2+eps) l=mid1;
        else if (res2>res1+eps) r=mid2;
        else break;
    }
    printf("%.15lf\n",(double)res1);
    return 0;
}

E

题目分析:
虽然是中文题面,但是是本套题目中最难的一题了。

题目定义一个长度为奇数的区间的值为其所包含的的元素的中位数。

现给出n个数,求将所有长度为奇数的区间的值排序后,第K大的值为多少。

思路,二分答案,对于每个答案t,判断答案是否是第K大。

对于原数组每个数,如果大于答案t,记录该点值为1,小于t的为-1,等于t的为0;那么,如果区间中位数大于t,则区间和大于0,中位数小于t,则区间和小于0。

于是判断区间和大于0的区间个数是否小于等于k;维护前缀和sum(i),然后统计之前sum(j)小于sum(i)的有多少个,就是以i为右值的区间和大于0的个数。

于是就可以用树状数组维护了。由于是奇数长度区间,所以树状数组需要维护奇偶长度的前缀和个数。需要特判sum(i) > 0的情况。
代码:
#include<bits/stdc++.h>
#define LL long long
using namespace std;
const int maxN = 100010;

LL d[2][maxN*2];

int lowbit(int x)
{
    return x&(-x);
}

void add(int t, int id,int pls)
{
    while(id <= maxN<<1)//id最大是maxN
    {
        d[t][id] += pls;
        id += lowbit(id);
    }
}

LL sum(int t, int to)
{
    LL s = 0;
    while(to > 0)
    {
        s = s + d[t][to];
        to -= lowbit(to);
    }
    return s;
}

LL query(int t, int from, int to)
{
    return sum(t, to) - sum(t, from-1);
}

int n, a[maxN], b[maxN];
LL k;

LL judge(int t)
{
    for (int i = 0; i < n; ++i)
    {
        if (a[i] > t) b[i] = 1;
        else if (a[i] == t) b[i] = 0;
        else b[i] = -1;
    }
    memset(d, 0, sizeof(d));
    int sum = 0;
    LL ans = 0;
    for (int i = 0; i < n; ++i)
    {
        sum += b[i];
        ans += query(!(i%2), 100005-n, 100005+sum-1);
        if (i%2 == 0 && sum > 0) ans++;
        add(i%2, 100005+sum, 1);
    }
    return ans;
}

void work()
{
    int lt, rt, mid;
    lt = rt = a[0];
    for (int i = 1; i < n; ++i)
    {
        lt = min(lt, a[i]);
        rt = max(rt, a[i]);
    }
    while ((LL)lt+1 < rt)
    {
        mid = ((LL)lt+rt)>>1;
        if (judge(mid) > k-1) lt = mid;
        else rt = mid;
    }
    if (judge(lt) <= k-1) printf("%d\n", lt);
    else printf("%d\n", rt);
}

int main()
{
    //freopen("test.in", "r", stdin);
    while (scanf("%d", &n) != EOF)
    {
        cin >> k;
        for (int i = 0; i < n; ++i) scanf("%d", &a[i]);
        work();
    }
    return 0;
}

F

题目分析:
F题给定一个带权有向联通图,题意为1点为起始点,2~n点代表车站,从起始点出发向每个车站派出一人再到起始点,边权代表费用,要求输出最小费用。

因为要求最小费用,对于任意一个车站节点而言,就是求从1节点到自己的最短路以及自己到1节点的最短路,其边权和为到达该节点并返回的人所花费的费用,最后对所有点的费用求个总和即可。

因为节点数不超过10^6,且我们只需求每个节点到1节点的最短路,故采取dijkstra算法求最短路。

流程就是先用原图求一遍dis,则所有的dis保存的是从1节点到每个点的最短路,即来时的费用。

然后将图反向再求一遍dis,则所有的dis保存的是从每个节点去往1节点的最短路,即回头时的费用。

将两次所有dis求和即为答案。

因为节点数不超过10^6,为防止爆内存,我们采用邻接表的方式保存这个图。
代码:
#include<bits/stdc++.h>
using namespace std;
const int maxn = 1000000+5;

struct node
{
    int from;
    int to;
    int length;
    int next;
};

struct pa
{
    int x;
    int z;
    bool operator < (const pa &a) const
    {
        if(z==a.z) return x<a.x;
        else return z > a.z;
    }
    pa() {}
    pa(int _x,int _z):x(_x),z(_z){}
};



int pinq[maxn],d[maxn];
node e[maxn];
int head[maxn];
int n,m,s,t;

void dijkstra()
{
    priority_queue <pa> pq;
    pa temp;temp.x=s,temp.z=0;
    pq.push(temp),d[s]=0;
    while(!pq.empty())
    {
        pa now = pq.top();pq.pop();
        if(pinq[now.x]==1)
        {
            continue;
        }
        pinq[now.x]=1;
        for(int i=head[now.x];i!=-1;i=e[i].next)
        {
            int y=e[i].to,z=e[i].length;
            if(d[y]>d[now.x]+z)
            {
                d[y]=d[now.x]+z;
                pq.push(pa(y,d[y]));
            }
        }
    }
}

void init()
{
    for(int i=0;i<maxn;i++) head[i]=-1;
    for(int i=0;i<maxn;i++) pinq[i]=0,d[i]=1e9;
}

int main()
{
    scanf("%d",&t);
    while(t--)
    {
        scanf("%d%d",&n,&m);
        init();
        for(int i=0;i<m;i++)
        {
            int x,y,z;
            scanf("%d%d%d",&x,&y,&z);
            e[i].from=x;
            e[i].to=y;
            e[i].length=z;
            e[i].next=head[x];
            head[x]=i;
        }
    /*for(int i=1;i<=n;i++)
            for(int j=head[i];j!=-1;j=e[j].next)
                cout<<i<<' '<<e[j].to<<' '<<e[j].length<<endl;*/
        s=1;
        dijkstra();
        int ans=0;
        for(int i=1;i<=n;i++)
            ans+=d[i];
        int temp;
        init();
        for(int i=0;i<m;i++)
        {
            temp=e[i].from;
            e[i].from=e[i].to;
            e[i].to=temp;
            e[i].next=head[e[i].from];
            head[e[i].from]=i;
        }
        dijkstra();
        for(int i=1;i<=n;i++)
            ans+=d[i];
        printf("%d\n",ans);
    }
    return 0;
}

G

题目分析:
给一个矩阵的每行和每列的和,(给的是前i行或者列的和);

矩阵中每个元素的值在1到20之间,找出这样的一个矩阵;

一道经典的二分图+有下界网络流的题目,由于本人还未看过,从网上copy了代码。

把它转化成一个二分图,每行和每列之间连一条弧,然后设置一个源点和一个汇点,源点与行点相连,汇点与列点相连,求一个最大值,当然这是一个有下界的最大流,需要做-1的处理,同时与源汇相连的边也是要处理的;最后求得的反向边+1就是答案了。
代码:
#include <cstdio>  
#include <cstring>  
#include <queue>  
#include <algorithm>  
using namespace std;  

const int MAXNODE = 105 * 2;  
const int MAXEDGE = 100005;  

typedef int Type;  
const Type INF = 0x3f3f3f3f;  

struct Edge {  
    int u, v;  
    Type cap, flow;  
    Edge() {}  
    Edge(int u, int v, Type cap, Type flow) {  
        this->u = u;  
        this->v = v;  
        this->cap = cap;  
        this->flow = flow;  
    }  
};  

struct Dinic {  
    int n, m, s, t;  
    Edge edges[MAXEDGE];  
    int first[MAXNODE];  
    int next[MAXEDGE];  
    bool vis[MAXNODE];  
    Type d[MAXNODE];  
    int cur[MAXNODE];  

    void init(int n) {  
        this->n = n;  
        memset(first, -1, sizeof(first));  
        m = 0;  
    }  
    void add_Edge(int u, int v, Type cap) {  
        edges[m] = Edge(u, v, cap, 0);  
        next[m] = first[u];  
        first[u] = m++;  
        edges[m] = Edge(v, u, 0, 0);  
        next[m] = first[v];  
        first[v] = m++;  
    }  

    bool bfs() {  
        memset(vis, false, sizeof(vis));  
        queue<int> Q;  
        Q.push(s);  
        d[s] = 0;  
        vis[s] = true;  
        while (!Q.empty()) {  
            int u = Q.front(); Q.pop();  
            for (int i = first[u]; i != -1; i = next[i]) {  
                Edge& e = edges[i];  
                if (!vis[e.v] && e.cap > e.flow) {  
                    vis[e.v] = true;  
                    d[e.v] = d[u] + 1;  
                    Q.push(e.v);  
                }  
            }  
        }  
        return vis[t];  
    }  

    Type dfs(int u, Type a) {  
        if (u == t || a == 0) return a;  
        Type flow = 0, f;  
        for (int &i = cur[u]; i != -1; i = next[i]) {  
            Edge& e = edges[i];  
            if (d[u] + 1 == d[e.v] && (f = dfs(e.v, min(a, e.cap - e.flow))) > 0) {  
                e.flow += f;  
                edges[i^1].flow -= f;  
                flow += f;  
                a -= f;  
                if (a == 0) break;  
            }  
        }  
        return flow;  
    }  

    Type Maxflow(int s, int t) {  
        this->s = s; this->t = t;  
        Type flow = 0;  
        while (bfs()) {  
            for (int i = 0; i < n; i++)  
                cur[i] = first[i];  
            flow += dfs(s, INF);  
        }  
        return flow;  
    }  
} gao;  

const int N = 105;  

int t, n, m, ans[N][N];  

int main() {  
    int cas = 0;  
    scanf("%d", &t);  
    while (t--) {  
        scanf("%d%d", &n, &m);  
        gao.init(n + m + 2);  
        int pre = 0, tmp;  
        for (int i = 1; i <= n; i++) {  
            scanf("%d", &tmp);  
            gao.add_Edge(0, i, tmp - pre - m);  
            pre = tmp;  
        }  
        pre = 0;  
        for (int i = 1; i <= m; i++) {  
            scanf("%d", &tmp);  
            gao.add_Edge(i + n, n + m + 1, tmp - pre - n);  
            pre = tmp;  
        }  
        for (int i = 1; i <= n; i++)  
            for (int j = 1; j <= m; j++)  
                gao.add_Edge(i, j + n, 19);  
        gao.Maxflow(0, n + m + 1);  
        printf("Matrix %d\n", ++cas);  
        for (int i = 1; i <= n; i++) {  
            for (int j = gao.first[i]; j != -1; j = gao.next[j]) {  
                Edge e = gao.edges[j];  
                if (e.v > n && e.v <= n + m)  
                    ans[i][e.v - n] = e.flow;  
            }  
        }  
        for (int i = 1; i <= n; i++) {  
            for (int j = 1; j <= m; j++)  
                printf("%d%c", ans[i][j] + 1, j == m ? '\n' : ' ');  
        }  
    }  
    return 0;  
}  

H

题目分析:
给一个数n,每次操作为开根向下取整,问5次操作之内能否得到1,若能输出操作次数,不能输出TAT。

由于4294967296开5次根号为2,因此大于10位的数字一定无法在5次操作内得到1,我们只需要对小于10位的数字按要求开方,判断5次以内能否得到1即可。

注意特判0。
代码:
#include<bits/stdc++.h>
using namespace std;

struct node
{
    char num[1005];
    long long n;
}a;

void getn(int len)
{
    a.n=0;
    for(int i=0;i<len;i++)
    {
        a.n = a.n*10 + (a.num[i]-'0');
    }
}
int main()
{
    while(scanf("%s",a.num)!=EOF)
    {
        int len = strlen(a.num);
        if(len > 10)
        {
            printf("TAT\n");
            continue;
        }
        getn(len);
        if(a.n == 0)
        {
            printf("TAT\n");
            continue;
        }
        long long temp = a.n;
        int point = 0;
        while(temp>1)
        {
            temp = (sqrt(temp));
            point ++;
        }
        if(point<=5) printf("%d\n",point);
        else printf("TAT\n");
    }
    return 0;
}

I

题目分析:
题目大意,给定一个长字符串string,长度小于1 e5;然后给t个字符串word,对于每个字符串,判断是否可以在string中找到两个不交叉区间,使得两区间内全部字符一次连接后与该字符串相同。两区间可以相邻。最后输出满足条件的word个数;

思路,对每个给定字符串进行一次KMP匹配,并记录下第一次匹配到word中i位置时在string中对应的位置j;然后将string与word反向;再次进行KMP,记录反向匹配到i位置时在string中对应的j位置;若两次j位置不交叉,则可判定该字符串满足条件。
代码:
#include <bits/stdc++.h>
using namespace std;
#define pb(x) push_back(x)
#define ll long long
#define mk(x, y) make_pair(x, y)
#define lson l, m, rt<<1
#define mem(a) memset(a, 0, sizeof(a))
#define rson m+1, r, rt<<1|1
#define mem1(a) memset(a, -1, sizeof(a))
#define mem2(a) memset(a, 0x3f, sizeof(a))
#define rep(i, n, a) for(int i = a; i<n; i++)
#define fi first
#define se second
typedef pair<int, int> pll;
const double PI = acos(-1.0);
const double eps = 1e-8;
const int mod = 1e9+7;
const int inf = 1061109567;
const int dir[][2] = { {-1, 0}, {1, 0}, {0, -1}, {0, 1} };
string s, str;
int nextt[1005], dp[100005];
void initKmp() {
    nextt[0] = -1;
    int i = 0, j = -1, len = str.size();
    while(i<len) {
        if(j==-1||str[i]==str[j])
            nextt[++i] = ++j;
        else
            j = nextt[j];
    }
}
int solve() {
    cin>>str;
    if(str.size()<=1)
        return 0;
    initKmp();
    mem1(dp);
    int i = 0, j = 0, len1 = s.size(), len2 = str.size();
    while(i<len1) {
        if(j==-1||s[i]==str[j]) {
            i++, j++;
        } else {
            j = nextt[j];
        }
        if(j == len2)
            return 1;
        if(j>=0&&dp[j]==-1)
            dp[j] = i;
    }
    reverse(s.begin(), s.end());
    reverse(str.begin(), str.end());
    initKmp();
    i = 0, j = 0;
    while(i<len1) {
        if(j == -1 || s[i]==str[j]) {
            i++, j++;
        } else {
            j = nextt[j];
        }
        if(j>=0&&dp[len2-j]!=-1 && dp[len2-j]<=len1-i) {
            reverse(s.begin(), s.end());
            return 1;
        }
    }
    reverse(s.begin(), s.end());
    return 0;
}
int main()
{
    cin>>s;
    int n, ans = 0;
    cin>>n;
    for(int i = 0; i<n; i++) {
        ans += solve();
    }
    cout<<ans<<endl;
    return 0;
}

J

题目分析:
由题意可知想求一段区间所能吃掉的蚂蚁个数即求该区间的最大公约数,个数即为该区间内与最大公约数不同的数字的个数。

这是道典型的线段树 但需要维护两个关键字

一个关键字是gcd,即最大公约数

一个节点的gcd的求法是看左儿子和右儿子的gcd,若两者相同,则父节点的gcd为其子节点的gcd,若不同,则父节点的gcd为左儿子和右儿子的gcd的最大公约数。求最大公约数可以用辗转相除的算法,该算法在c++库里可直接调用,函数名为__gcd,(注意这里是两个连续的下划线)。

另一个关键字是num,用来保存该节点所示区间中与该区间gcd相同的数字的个数

一个节点的num的求法是看左儿子和右儿子的num和gcd,当两个子节点的gcd相同时,父节点的num即两个子节点num的和,若不同时需要分情况:①父节点的gcd与其中一个子节点的gcd相同,则父节点的num与该子节点的num相同②父节点的gcd与两个子节点的gcd均不同,则父节点的num=0.

建树完毕之后,这里我使用两个query(query和query2)来求答案

query用来求这个区间的最大公约数key

query2在用query求得key的基础上求该区间上和此key相同的数字的个数

最后我们输出剩下的数,即该区间数字的总个数-query2得到的个数

注:线段树算法不会的可百度自学。
代码:
#include<bits/stdc++.h>
using namespace std;
const int maxn = 100005;
struct tt
{
    int gcd,num;
};
tt tree[maxn*4];
int a[maxn];
void build(int n,int l,int r)
{
    if(l==r)//叶节点的建立,叶节点的gcd即该数,num=1
    {
        tree[n].gcd=a[l];
        tree[n].num=1;
    }
    else
    {
        int mid=(l+r)/2;
        build(n*2,l,mid);
        build(n*2+1,mid+1,r);
        if(tree[n*2].gcd==tree[n*2+1].gcd)
        {
            tree[n].gcd=tree[n*2].gcd;
            tree[n].num=tree[n*2].num+tree[n*2+1].num;
        }
        else
        {
            tree[n].gcd=__gcd(tree[2*n].gcd,tree[n*2+1].gcd);
            if(tree[n].gcd==tree[2*n].gcd)
                tree[n].num=tree[2*n].num;
            else
                if(tree[n].gcd==tree[2*n+1].gcd)
                    tree[n].num=tree[2*n+1].num;
            else
            tree[n].num=0;
        }
    }
}
int query(int n,int l,int r,int begin ,int end)//用来求从begin到end区间内的最大公约数
{
    if(l>=begin&&r<=end) return tree[n].gcd;
    else{
        int mid=(l+r)/2;
        if(mid+1>end)
        {
            return query(n*2,l,mid,begin,end);
        }
        if(mid<begin)
        {
            return query(n*2+1,mid+1,r,begin,end);
        }
        return __gcd(query(n*2,l,mid,begin,end),query(n*2+1,mid+1,r,begin,end));
    }
}
int query2(int n,int l,int r,int begin,int end,int key)//用来求该区间上和key相同的数字的个数
{
    if(l>=begin&&r<=end)
    {
        if(key==tree[n].gcd)
            return tree[n].num;
        else
            return 0;
    }
    else{
        int mid=(l+r)/2;
        int ans=0;
        if(mid>=begin)
        {
            ans+=query2(n*2,l,mid,begin,end,key);
        }
        if(mid+1<=end)
        {
            ans+=query2(n*2+1,mid+1,r,begin,end,key);
        }
        return ans;

    }
}
int n,m;
int main()
{
    cin>>n;
    for(int i=1;i<=n;i++)
        cin>>a[i];
    cin>>m;
    build(1,1,n);
    while(m--)
    {
        int x,y,key;
        cin>>x>>y;
        key=query(1,1,n,x,y);
        cout<<y-x+1-query2(1,1,n,x,y,key)<<endl;
    }
return 0;
}   
注:本人想过应该可以把两个query合并成一个query,大家可以尝试一下,应该不难,这里就不多说啦。

K

题目分析:
给定两个1e18以内的正整数l,r,要求输出它们之间的满足首位与末尾相同的数(如1,11,121,1201)的个数。

由于为闭区间,找到1-r范围内的数的个数,与1-(l-1)范围内的数的个数,它们的差即为answer。

找1 - x 之间的满足题意的个数可以分为3个部分。

例如1- 2024之间:

    找到1-999范围内的部分;

    找到1000-1999范围内的部分;

    找到2000-2024之间的部分;

即:

    找到1 -  (1ei-1)范围内的部分;

    找到1ei - k*1ei范围内的部分;

    找到 k*1ei - x范围内的部分;
代码:
#include<bits/stdc++.h>
using namespace std;

int getlen(long long x)
{
    int  ans= 0;
    while(x>0)
    {
        ans++;
        x/=10;
    }
    return ans;
}
int getl(long long x)
{
    while(x>=10)
    {
        x/=10;
    }
    return (int)x;
}
int getr(long long x)
{
    int ans = (int)(x%10);
    return ans;
}

long long firsts(int len)
{
    long long ans=0,temp=10;
    for(int i=1;i<len;i++)
    {
        if(i>2)
        {
            ans+=temp*9;
            temp*=10;
        }
        else
            ans+=9;
    }
    return ans;
}
long long seconds(int len)
{
    long long temp=1;
    for(int i=3;i<=len;i++)
    {
            temp*=10;
    }
    return temp;
}
long long lasts(int len,long long num)
{
    int a[len+1];
    for(int i=1;i<=len;i++)
    {
        a[len+1-i]= num%10;
        num /=10;
    }
    long long ans=0;
    for(int i=2;i<=len-1;i++)
    {
        ans = ans*10 + a[i];
    }
    ans++;
    return ans;
}
long long letdo(long long num)
{
    if(num==0)return 0;
    int len = getlen(num);
    int l = getl(num);
    int r = getr(num);
    long long ans=0;

    ans += firsts(len);
    ans += (l-1) * seconds(len);
    ans += lasts(len,num);
    if(r<l) ans--;
    return ans;
}

int main()
{
    long long x,y;
    while(scanf("%I64d%I64d",&x,&y)!=EOF)
    {
        long long ansl=letdo(x-1);
        long long ansr=letdo(y);
        printf("%I64d\n",ansr-ansl);
    }

    return 0;
}

L

题目分析:
签到题,注意输出格式。
代码:
#include<bits/stdc++.h>
using namespace std;

int main()
{
    long long n;
    while(scanf("%lld",&n)!=EOF)
    {
        long long ans=0;
        for(int i=1;i<=n;i++)
            ans+=i;
        printf("%lld\n\n",ans);
    }
    return 0;
}
ACM/ICPC(ACM International Collegiate Programming Contest, 国际大学生程序设计竞赛)是由国际计算机界历史悠久、颇具权威性的组织ACM(Association for Computing Machinery,国际计算机协会)主办的,世界上公认的规模最大、水平最高的国际大学生程序设计竞赛,其目的旨在使大学生运用计算机来充分展示自己分析问题和解决问题的能力。该项竞赛从1970年举办至今已历29届,一直受到国际各知名大学的重视,并受到全世界各著名计算机公司的高度关注,在过去十几年中,APPLE、AT&T、MICROSOFT和IBM等世界著名信息企业分别担任了竞赛的赞助商。可以说,ACM国际大学生程序设计竞赛已成为世界各国大学生最具影响力的国际级计算机类的赛事,是广大爱好计算机编程的大学生展示才华的舞台,是著名大学计算机教育成果的直接体现,是信息企业与世界顶尖计算机人才对话的最好机会。   该项竞赛分区域预赛和国际决赛两个阶段进行,各预赛区第一名自动获得参加世界决赛的资格,世界决赛安排在每年的3~4月举行,而区域预赛安排在上一年的9~12月在各大洲举行。   ACM/ICPC的区域预赛是规模很大、范围很广的赛事。仅在2003年参加区域预赛的队伍就有来自75个国家(地区),1411所大学的3150支代表队,他们分别在127个赛场中进行比赛,以争夺全球总决赛的73个名额,其激烈程度可想而知。 2005年第30届ACM/ICPC亚洲赛区预赛共设了北京、成都、汉城、东京等11个赛站,来自亚洲各国知名高校的各个代表队进行了激烈的角逐。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值