欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

EOJ Monthly 2017.12 (暨 ECNU 12 月内部选拔) 题解

程序员文章站 2024-02-08 14:33:04
...

比赛链接

题解链接

A 水题,模拟即可

B. 在哈尔滨的寒风中
Time limit per test: 1.0 seconds
Memory limit: 256 megabytes
kblack 来到了寒冬中的哈尔滨,哈尔滨的寒风令 kblack 瑟瑟发抖。

世界上最远的距离,是你与宾馆只差一条冰街,而你却忘了穿上秋裤。

kblack 终于冲进了宾馆,宾馆大厅的地板铺满了五颜六色的地砖,可以被看作是一块 n×m 格的棋盘,为了能使冻僵了的双脚尽快暖和起来,kblack 决定在地砖上走动,但是他被速冻的双脚在棋盘地板上只能走马步。

kblack 居然想知道有多少对地砖(无序点对)他可以通过若干步马步互相抵达!
Input
输入包含一行两个正整数 n, m,表示棋盘的大小,保证 1≤n×m≤109 。

Output
输出包含一个整数,表示 kblack 可以通过马步互相到达的无序地砖对数。

Examples
input
1 2
output
0
input
4 2
output
4
思路:
第2次尝试用大数,比赛时候没有发现n*m<=1e9,最后其实不用大数也能过,
虽然限制只能走马步,但我们很容易意识到在足够大的棋盘(例如象棋棋盘)上,马可以达到任何位置。事实上通过简单的验证,可以发现这一大小的下界是 3×4。

于是对于所有 ≥3×4 的棋盘,我们可以断言所有砖之间可以互相到达,此时答案为 (nm2)。

当棋盘大小为 3×3 时,通过简单的模拟可以发现外围的 8 块砖可以互相到达,此时答案为 (82)。

当棋盘大小为 2×n 时,我们发现不同奇偶不同的行/列交替可达,此时有 2 组 ⌊n/2⌋ 的联通块与两组 n−⌊n/2⌋ 的联通块,答案为 2×(⌊n/2⌋2)+2×(n−⌊n/2⌋2)。

当棋盘大小为 1×n 时,没有合法的马步,此时答案为 0。

注意答案可能超过 2147483647,需要使用 long long 类型。
c++

#include <cstdio>
#include <algorithm>
using namespace std;
typedef long long ll;

int main()
{
    ll n, m;
    scanf("%lld%lld", &n, &m);
    if (n == 1 || m == 1) printf("0\n");
    else if (n == 2 || m == 2){
        n = max(n, m);
        printf("%lld\n", (n/2)*(n/2-1)+(n-n/2)*(n-n/2-1));
    }
    else if (n == 3 && m == 3) printf("28\n");
    else printf("%lld\n", n*m*(n*m-1)/2);
    return 0;
}

java

import java.math.BigInteger;
import java.util.*;

public class Main {
    private static Scanner scan;

    public static void main(String[] args) {
        scan = new Scanner(System.in 

);
            BigInteger n,m,u1,u2,u3;
                n=scan.nextBigInteger();
                m=scan.nextBigInteger();
            BigInteger t1 = BigInteger.valueOf(1);
            BigInteger t2 = BigInteger.valueOf(2);
            BigInteger t3 = BigInteger.valueOf(3);
        //  System.out.println(n);
            if(n.equals(t1)||m.equals(t1))
                System.out.println("0");
            else if(n.equals(t3)&&m.equals(t3))
                System.out.println("28");
            else if(n.equals(t2))
            {
                u1 = m.subtract(t1);
                u2 = m.subtract(t2);
                u1 = u1.multiply(u2);
                u1 = u1.divide(t2);
                 u3 = m.subtract(t3);
                 u3 = u3.add(t2);
                 u3 = u3.divide(t2);
                 u3 = u3.add(u1);
                 System.out.println(u3);
            }

            else if(m.equals(t2))
            {
                u1 = n.subtract(t1);
                u2 = n.subtract(t2);
                u1 = u1.multiply(u2);
                u1 = u1.divide(t2);
                 u3 = n.subtract(t3);
                 u3 = u3.add(t2);
                 u3 = u3.divide(t2);
                 u3 = u3.add(u1);
                 System.out.println(u3);
            }

            else
            {
                n  = n.multiply(m);
                //System.out.println(n);
                m = n;
                //System.out.println(m);
                m = m.subtract(t1);
                //System.out.println(m);

                //System.out.println(m);
                n = n.multiply(m);
                n = n.divide(t2);
                System.out.println(n);
            }             
    }
}

G1. 唐纳德与子串 (Easy)
Time limit per test: 1.0 seconds
Memory limit: 256 megabytes
子串的定义是在一个字符串中连续出现的一段字符。这里,我们使用 s[l…r] 来表示 s 字符串从 l 到 r(闭区间)的子串。在本题中,字符串下标从 0 开始。显然,对于长度为 n 的字符串共有 n(n+1)2 个子串。

对于一个给定的字符串 s,唐纳德给出 q 次询问,第 i 次询问包括三个参数 li,ri,zi,问在 s[li…ri] 的所有子串*有多少个恰好为 zi。

Input
输入具有如下形式:

sql1 r1 z1l2 r2 z2⋮lq rq zq

第一行一个字符串 s。

第二行一个整数 q。

接下来每行:首先两个整数 li,ri (0≤li≤ri<|s|),然后是一个非空字符串 zi。整数和整数,整数和字符串间以单空格隔开。

字符串中只会出现 26 个小写英文字母。

数据规模约定:

对于 Easy 档:1≤|s|≤100,q≤∑|zi|≤100。
对于 Hard 档:1≤|s|≤105,q≤∑|zi|≤105。
Output
对于每次询问,输出一个整数,表示答案。

Examples
input
thisisagarbagecompetitionhahaha
5
0 30 a
1 5 is
25 30 hah
6 12 ag
7 12 ag
output
6
2
2
2
1

#include <iostream>
#include<bits/stdc++.h>
using namespace std;
char s[900],ss[200];
unsigned int h[2000],h2[200],base[200];
inline void init_hash(int l,char *s,unsigned int*h)
{
    h[0] = 0;
    for(int i=1;i<=l;i++)
        h[i] = h[i-1]*131+s[i-1];
    base[0] = 1;
    for(int i=1;i<=l;i++)
        base[i] = base[i-1]*131;
}
inline unsigned int str(unsigned int*h,int l,int r)
{
    return h[r]-h[l]*base[r-l];
}
int main()
{
    cin>>s;
    init_hash(200,s,h);
    int n;
    cin>>n;
    for(int i=1;i<=n;i++)
    {
        int l,r;
        cin>>l>>r;
        unsigned int p1 = str(h,l,r);
       // cout<<p1<<endl;
        cin>>ss;
        int len = strlen(ss);
        init_hash(len,ss,h2);
        unsigned int p2 = str(h2,0,len);
        //cout<<p2<<endl;
        int sum = 0;
        for(int ii=l;ii<=r+1;ii++)
        {
            for(int j=ii+1;j<=r+1;j++)
            {
                     unsigned int p1 = str(h,ii,j);
                     if(p1==p2)sum++;
            }
        }
        cout<<sum<<endl;
    }

    return 0;
}

C:

Prepared by ultmaster.

我们可以先考虑字符串有序的情况,比如是 aaabcc,我们只要将字符串右移 3 位,变成 bccaaa,就做完了。那么对于无序的情况,我们可以通过排序让它有序,做完之后再排回去。

显然最多的字母出现次数大于一半的情况是不行的。否则就将每个字母的位置和字母绑定一下,按字母序对结构体进行排序。然后右移「出现最多的字母出现次数」位,再排回来就可以了。时间复杂度 O(nlogn)。

也可以对 26 个字母进行遍历,把每个字母填到合适的位置。具体细节留给读者思考。

#include <iostream>
#include<bits/stdc++.h>
using namespace std;
struct node
{
    int i;
    char e;
    char ee;
}s[100010];
char ss[100010];
int cmp(node a,node b)
{
    return a.e-'a'<b.e-'a';
}
int cmp2(node a,node b)
{
    return a.i<b.i;
}
int main()
{
   scanf("%s",ss);
   int book[200]={0};
   int len = strlen(ss);
   for(int i=0;ss[i];i++)
   {
       s[i].i = i;
       s[i].e = ss[i];
       book[ss[i]-'a'+1]++;
   }
   int mm = 0;
   int ans ;
   for(int i=1;i<=26;i++)
   {
        if(book[i]>mm)
        {
            mm = book[i];
            ans = i;
        }
   }
   if(mm>len/2)
    printf("impossible\n");
   else
   {
       //cout<<mm<<endl;
       sort(s,s+len,cmp);
//       for(int i=0;i<len;i++)
//       {
//           printf("%c",s[i].e);
//       }
//       cout<<endl;
       for(int i=0;i<len;i++)
       {

           s[i].ee = s[(mm+i)%len].e;
       }
       sort(s,s+len,cmp2);
       for(int i=0;i<len;i++)
       {
           printf("%c",s[i].ee);
       }
   }

    return 0;
}

D 二分匹配 匈牙利匹配模板+因子分解(bzoj锦囊问题)
Time limit per test: 1.0 seconds
Memory limit: 256 megabytes
唐纳德是一个数学天才。有一天,他的数学老师决定为难一下他。他跟唐纳德说:「现在我们来玩一个游戏。这个游戏总共 n 轮,每一轮我都会给你一个数(第 i 轮给出的数是 ai)。你每次要回答一个数,是我给出的这个数的质因数,并且你说出的数不能重复。」

因为数学老师是刻意为难,所以这个游戏很有可能不可能进行到最后。但是聪明的数学老师早就已经知道这个游戏最多能进行几轮了。现在他把问题抛给了你,想看看你知不知道。

注意,1 不是质数。

Input

输入具有如下形式:

na1 a2 … an

第一行一个整数 n (1≤n≤3 000)。

第二行 n 个整数用空格隔开,a1,a2,…,an (2≤ai≤106)。

Output

输出游戏最多能进行几轮。

Examples
input
3
7 6 3
output
3
input
5
2 2 2 2 2
output
1
Source

EOJ Monthly 2017.12

题解:

又到了喜闻乐见的套路题时刻。

一个很显然的想法是对左边的 n 轮和右边个质因数之间建边,便是一个二分图,就是做匹配。但是很遗憾,朴素的匹配是错误的,因为存在一个顺序问题。正确的做法是有一次如果不能找到匹配,就直接跳出。这是一个非常经典的问题,具体证明可以自行搜索 BZOJ 锦囊问题。

求素数可以使用朴素的 O(n−−√) 做法,用素数筛筛过头了可能反而会超时。因为素数不多,但素数的范围比较大,如果直接照抄板子每次都 memset 可能会超时,可以打时间戳,或者直接对质数进行离散化。

也可以二分答案直接跑二分图,姿势好一点也能过。

#include<bits/stdc++.h>
using namespace std;
const int maxn = 1e6+7;
vector<int> v[maxn],g[maxn];
int vis[maxn],a[maxn],l[maxn];
bool dfs(int u)
{
    for(int i=0;i<g[u].size();i++)
    {
        int j = g[u][i];
        if(!vis[j])
        {
            vis[j]  =true;
            if(l[j]==-1||dfs(l[j]))
            {
                l[j] = u;
                return true;
            }
        }
    }
    return false;
}
int main()
{
    int n;
    cin>>n;
    int ans;
    memset(l,-1,sizeof(l));
    for(int i=1;i<=n;i++)
    {
         scanf("%d",&a[i]);
         if(a[i]==1)
         {
             n = i-1;//出现1即跳出即可
             break;
         }
         for(int j=2;j*j<=a[i];++j)
         {
             while(a[i]%j==0)
             {
                 v[i].push_back(j);
                 a[i]/=j;
             }
         }
         if(a[i]!=1)v[i].push_back(a[i]);
    }
         if(n==0)
         {
             puts("0");
             return 0;
         }
         for(int i=1;i<=n;i++)
         {
            for(int j=0;j<v[i].size();j++)
            {
                g[i].push_back(v[i][j]);
            }
            memset(vis,false,sizeof(vis));
            if(!dfs(i))break;
            else ans = i;
         }
         cout<<ans<<endl;


    return 0;
}

【bzoj1191】[HNOI2006]超级英雄Hero

Description

现在电视台有一种节目叫做超级英雄,大概的流程就是每位选手到台上回答主持人的几个问题,然后根据回答问题的多少获得不同数目的奖品或奖金。主持人问题准备了若干道题目,只有当选手正确回答一道题后,才能进入下一题,否则就被淘汰。为了增加节目的趣味性并适当降低难度,主持人总提供给选手几个“锦囊妙计”,比如求助现场观众,或者去掉若干个错误答案(选择题)等等。 这里,我们把规则稍微改变一下。假设主持人总共有m道题,选手有n种不同的“锦囊妙计”。主持人规定,每道题都可以从两种“锦囊妙计”中选择一种,而每种“锦囊妙计”只能用一次。我们又假设一道题使用了它允许的锦囊妙计后,就一定能正确回答,顺利进入下一题。现在我来到了节目现场,可是我实在是太笨了,以至于一道题也不会做,每道题只好借助使用“锦囊妙计”来通过。如果我事先就知道了每道题能够使用哪两种“锦囊妙计”,那么你能告诉我怎样选择才能通过最多的题数吗?

Input

输入文件的一行是两个正整数n和m(0 < n <1001,0 < m < 1001)表示总共有n中“锦囊妙计”,编号伟0~n-1,总共有m哥问题。
以下的m行,每行两个数,分别表示第m个问题可以使用的“锦囊妙计”的编号。
注意,每种编号的“锦囊妙计”只能使用一次,同一个问题的两个“锦囊妙计”可能一样。

Output

第一行为最多能通过的题数p

Sample Input

5 6
3 2
2 0
0 3
0 4
3 2
3 2
Sample Output

4

C++

#include<cstdio>
#include<iostream>
#include<cstring>
using namespace std;
int mp[1001][1001],lk[1001];
int n,m;
bool y[1001];
bool find(int x)
{
    for(int i=0;i<n;i++)
    {
       if(!y[i]&&mp[x][i])
       {
           y[i]=1;
           if(!lk[i]||find(lk[i]))
           {
              lk[i]=x;
              return 1;
           }
       }
    }
    return 0;
}
int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=m;i++)
    {
       int x,y;
       scanf("%d%d",&x,&y);
       mp[i][x]=mp[i][y]=1;
}
    int ans=0;
    for(int i=1;i<=m;i++)
    {
        memset(y,0,sizeof(y));
        if(find(i))ans++;
        else break;
    }
    printf("%d\n",ans);
    return 0;
}

E:

参考博客

E比昨天更多的棒棒糖(Easy+Hrad)(华师网络赛)(DP||母函数||背包优化)

Time limit per test: 2.0 seconds

Memory limit: 512 megabytes

唐纳德先生的某女性朋友最近与唐纳德先生同居。该女性朋友携带一 baby。该 baby 酷爱吃棒棒糖,且有一个奇怪的特性:今天吃的棒棒糖一定要比昨天的棒棒糖更多,至少要一样多。如果棒棒糖少了,baby 就会很不高兴;另外如果有连续 k 天棒棒糖的数量都是一样的,baby 也会很不高兴。

唐纳德先生发现他的口袋里只有可怜的 n 元钱,他可以用 1 元钱买 1 根棒棒糖。他想用这些钱逗 baby 开心,这些钱可以不花完。他可以从某一天开始再也不买棒棒糖,把他的女性朋友和 baby 一起送回家;但是他绝对不能让 baby 不高兴,否则他的女性朋友可能对他做一些不和谐的事情。

唐纳德先生想要知道,他总共有多少种买棒棒糖的方案,两种方案不相同当且仅当总天数不相同,或者某一天买的棒棒糖数量不相同。唐纳德先生知道这个问题对于聪明的你实在是太简单了,所以他加了一个附加条件:他第一天必须买棒棒糖,而且至少买 x 根棒棒糖。

Input

一行三个整数 n,x,k。

数据范围约定:

对于 Easy 档:1≤n,x≤100,2≤k≤100。
对于 Hard 档:1≤n,x≤104,2≤k≤104。
Output

输出答案模 998 244 353。

Examples

input

3 1 2
output

4
input

1 1 2
output

1
input

4 2 3
output

4
Note

样例 1:

有四种方案:

第一天 1;
第一天 2;
第一天 3;
第一天 1,第二天 2;
注意第一天和第二天都买 1 是不行的,因为连续两天棒棒糖数量一样,baby 就会很不高兴。

题意:

把n表示成a1*p1+a2*p2+a3*p3…的形式,且满足x<=a1


 Easy版本,普通母函数


#include<cstdio>
#include<cstdlib>
#include<iostream>
#include<memory.h>
#include<algorithm>
#include<cstring>
using namespace std;
const int Mod=998244353;
int c1[330],c2[330],ans,x,K,p;
int n,i,j,m,k;
void _get()
{  
    memset(c1,0,sizeof(c1));
    memset(c2,0,sizeof(c2));
    scanf("%d%d",&x,&K);
    for(k=0;k<K&&k*x<=n;k++) {
         c1[k*x]=1;
         ans=(ans+c1[k*x])%Mod;
    }
    for(i=x+1;i<=n;i++){
        for(j=0;j<=n;j++)
          for(k=0;k*i+j<=n&&k<K;k++) {
               c2[k*i+j]+=c1[j]; 
               if(k) ans=(ans+c1[j])%Mod;
            }
        for(k=0;k<=n;k++) {
            c1[k]=c2[k];    
            c2[k]=0;
        }
    }
    ans=(ans+Mod-1)%Mod;
}
int main()
{

    while(cin>>n) {
        ans=0;
        _get(); 
        cout<<ans<<endl;
    }
    return 0;
}


Hard版本,母函数优化背包。


include<cstdio>
include<cstdlib>
include<iostream>
include<cstring>
using namespace std;
const int Mod=998244353;
const int maxn=10100;
long long  a[maxn],b[maxn],c[maxn],ans;
int main()
{
    int n,x,k,i,j;
    scanf("%d%d%d",&n,&x,&k);
    a[0]=b[0]=1;

    for(i=x;i<=n;i++)
     for(j=n;j>=0;j--)
      if(j>=i*k) a[j]=(a[j]-a[j-i*k])%Mod;

    for(i=x;i<=n;i++)
     for(j=i;j<=n;j++)
       b[j]=(b[j]+b[j-i])%Mod;

    for(i=0;i<=n;i++)
     for(j=0;j<=n;j++)
      if(i+j<=n) c[i+j]=(c[i+j]+a[i]*b[j])%Mod;

     for(i=1;i<=n;i++)
       ans=((ans+c[i])%Mod+Mod)%Mod;

    printf("%lld\n",ans);
    return 0;
}