2015-2016 ACM-ICPC, NEERC, Northern Subregional Contest(夏季组队赛第三场)

A - Alex Origami Squares

题意:用给定一个长度和宽度组成的一个长方形纸,裁减出三个边长相等的正方形。
求这个正方形可能的最大边长。
在这里插入图片描述
思路:我们拿最一般的长方形纸( 假设 m > n m>n m>n),一个边 m m m 从无穷递减,一个边 n n n 从0 增大,当 n = m n=m n=m 时分析结束,因为 n n n m m m是对称的问题,所以我们假设当前长的边为 m m m 小的边为 n n n 即可。

m > = 3 ⋅ n 时 m>=3·n时 m>=3n,显然以左边 n = r n=r n=r 往右裁减三个正方形可以使得利用率最大!
在这里插入图片描述

3 ⋅ n > m > n 时 3·n>m>n时 3n>m>n,显然贴着底边 m 3 \dfrac{m}{3} 3m 来裁减利用率最大,此时最优解 r = m 3 r=\dfrac{m}{3} r=3m
在这里插入图片描述

继续以 m 减 小 m减小 m n 增 大 n增大 n的方向去分析裁减正方形的最优解。
m = n m=n m=n时,显然可以贴 n n n 的这边让 r = n 2 r=\dfrac{n}{2} r=2n取得最优解!
在这里插入图片描述

因为n和m是对称问题所以当n=m是分析的终点,又因为两条边总存在 m > = n 或 n > = m m>=n或n>=m m>=nn>=m这样的关系,所以我们如果假设 n n n 是较小的那条边,初始化 r = n 2 r=\dfrac{n}{2} r=2n总是成立的,以这个为最小值去分析 m m m 的范围看 r r r 是否能取得更大的值!

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
int main()
{
    freopen("alex.in","r",stdin);
	freopen("alex.out", "w", stdout);
    double n,m;
    cin>>n>>m;
    if(n>m)
    {
        int t=n;
        n=m;
        m=t;
    }
    double r=n/2;
    if(m>=3*n) r=max(r,n);
    else if(m>=n&&m<3*n) r=max(r,m/3);
    printf("%.3lf\n",r);
}

B - Black and White

题意:构造连通块,使得“@”的连通块为10,“.”的连通为7。
在这里插入图片描述
思路:先把小的那个数对称拉平,剩下的就构造一个大连通线,让多的那个数连通块渐渐补全。

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
int main()
{
    freopen("black.in","r",stdin);
	freopen("black.out", "w", stdout);
    int  n,m;
    cin>>n>>m;
    int x=min(n,m);
    if(n>m)
    {
        cout<<n*2<<' '<<2<<endl;
        int tt=x;
        while(x--)
        {
        cout<<"@@"<<endl;
        cout<<".."<<endl;
        }   
        int cnt=0;
        for(int i=tt;i<2*n-tt;i++)
        {
            if(cnt%2==0) cout<<".@"<<endl;
            else cout<<".."<<endl;
            cnt++;
        } 
    }
    
    else
    {
        cout<<m*2<<' '<<2<<endl;
        int tt=x;
        while(x--)
        {
        cout<<".."<<endl;
        cout<<"@@"<<endl;
        }
        int cnt=0;
        for(int i=tt;i<2*m-tt;i++)
        {
            if(cnt%2==0) cout<<".@"<<endl;
            else cout<<"@@"<<endl;
            cnt++;
        }    
    }
}

C - Concatenation

题意:给两个串,第一个串拿出它的所有前缀子串,第二个串拿出所有它的后缀子串,求两两拼凑的组合数。
在这里插入图片描述
思路:首先第一个样例是所有前后缀组合,答案是 3 ⋅ 3 = 9 3·3=9 33=9,观察第二个样例, t r e + a p = t r + e a p tre+ap=tr+eap tre+ap=tr+eap , t r e e + a p = t r e + e a p tree+ap=tre+eap tree+ap=tre+eap, 我们发现第一个串每有一个与第二个字符重复的字符就会多一个重复的答案,最后被最大组合数减去。
但是又因为这个重复字符前边有 t r tr tr 这个前缀,所以使得产生了 t r e + a p = t r + e a p tre+ap=tr+eap tre+ap=tr+eap这一种重复。但如果此时ee在最前面的话,就无法产生 e + a p = e+ap= e+ap=’ 空’+ e a p eap eap这种重复,所以记录第一个串字母出现次数的时候不要考虑第一个字符了。同理,第二个串的尾字符也不会对答案产生贡献。
所以我们可以把第一个串的出现次数存下来,遍历第二个串,然后每一个字符对应在第一个串的次数就是需要去掉的重复答案。

#include<bits/stdc++.h>
using namespace std;
typedef long long LL;

int main()
{
    freopen("concatenation.in","r",stdin);
	freopen("concatenation.out", "w", stdout);
    string st1,st2;
    
    cin>>st1>>st2;
    LL ans=(LL)st1.size()*(LL)st2.size();
    map<char,LL> mp1;
    map<char,LL> mp2;
    for(int i=1;i<st1.size();i++)
    {
        mp1[st1[i]]++;
    }   
    LL res=0;
    for(int i=0;i<st2.size()-1;i++)
    {   
        if(mp1[st2[i]]) 
        {
            res+=mp1[st2[i]];
        }
    }
    cout<<ans-res<<endl;    
    
}

D—Distribution in Metagonia

感谢队友将此题一遍AC掉并手把手教我写代码 ___ that’s means 我是废物
题意:给出一个数字n,让我们构造出不超过100个数的一个集合,使得其集合所有数的加和为 n n n
要求集合中的数字任取两个数字都是两两不能整除的,而且需要保证每个数都能写成 2 x ∗ 3 y 2^x*3^y 2x3y 的形式——因子只可能有2或3。

思路:对于任意一个 n ,如果它是偶数一定可以有2这个因子,如果它是奇数,要么是个质数要么可以被3这个因子除到一个质数。所以我们先把 n n n 写成这样一个形式:
n = 2 p ∗ 3 q ∗ w n=2^p*3^q*w n=2p3qw

代表的含义是,对于任何一个数 n n n,我们都可以把它分解为 2 的 p次方 * 3 的 q 次方 * 一个质数(奇数) 的形式。
此时:我们可以把 w 分 解 为 : w = w 1 + w 2 + w 3 + . . . w n w分解为:w=w_1+w_2+w_3+...w_n w:w=w1+w2+w3+...wn的形式再带入上式,那么对与这样的每一项 w i w_i wi只要不是倍数关系,那么最后得出的答案就会是满足 2 p ∗ 3 q 2^p*3^q 2p3q 的形式且两两不能整除的数。
到了这里分析的中心就是如何将 w 分 解 为 : w = w 1 + w 2 + w 3 + . . . w n w分解为:w=w_1+w_2+w_3+...w_n w:w=w1+w2+w3+...wn使其满足两两不能整除的关系:对于任意一个质数 w i w_i wi 我们依然不能忘记拆分的每一项 w i w_i wi 的原则还是要保证最后乘出来每一项都是 2 x ∗ 3 y 2^x*3^y 2x3y 的形式,你总不能把 11 分 解 成 5 + 6 11分解成 5+6 115+6,这样显然对于 5 ∗ 2 p ∗ 3 q 5 *2^p*3^q 52p3q 是不符合只含2或3这两种因子的条件的。所以我们可以把每一项 w i w_i wi 拆分成 3 k + m ( 偶 数 ) 3^k+m(偶数) 3k+m 的形式 【因为奇数 w i w_i wi 只能拆分成奇数+偶数,这里的 3 k 3^k 3k 是不超过 w i w_i wi的最大的一个3的幂 】。这个 3 k 3^k 3k乘前边的因子 2 x ∗ 3 y 2^x*3^y 2x3y 出去之后显然是一组独立的方案,它与这个偶数 m m m 继续拆得的一系列因数均互质——这里简单证明一下为什么这样拆可以使得两两不能整除,也正是因为这样拆使得了两两不能整除所以才这样拆:
证明
我们可以把某一种 2 x ∗ 3 y 2^x*3^y 2x3y当成一种系数假设为prime【i】。就可以得到这样一组递推关系式。 A = 3 k A = 3^k A=3k, m = p r i m e [ 1 ] ∗ ( B + C ) m=prime[1]*(B+C) m=prime[1](B+C) B B B还是 3 q 3^q 3q的含义, C C C还是偶数…
n = p r i m e [ 0 ] ∗ ( A + p r i m e [ 1 ] ∗ ( B + C ) ) — — C 可 以 继 续 拆 n=prime[0]*(A+prime[1]*(B+C))——C可以继续拆 n=prime[0](A+prime[1](B+C))C
因为 B B B C C C是被一个偶数拆出来的,所以prime[1]中必然含有 2 2 2这个因子,又因为 A = 3 k 必 然 是 一 个 奇 数 A=3^k必然是一个奇数 A=3k,对于这样的一个奇数不可能被一个带有 2 2 2 这个因子的任何一个数整数,又因为 3 k 3^k 3k 是不超过 n n n 的最大的一个3的幂,所以 m ( 这 个 偶 数 ) m(这个偶数) m() 往后拆的时候不可能拆出一个比 3 k 还 大 , 甚 至 整 除 3 k 次 方 的 数 3^k还大,甚至整除3^k次方的数 3k3k, 不然就跟 3 k 3^k 3k 是不超过 n n n 的最大的一个3的幂矛盾,因此A必然不能被后边的B,C…整除,后边的也不可能回过头来整除A,所以此时认为A已经是合法的一组数了,就可以把A乘出来输出了。
通过重复这个过程,通过不断的拆一定可以把所有的数都拆成这样的数,最后分解到 1 1 1 无法再拆了,就直接把 1 1 1乘出来一定也是一组合法的解。

#include <iostream>
#include <cstring>
#include <algorithm>
#include<vector>
const int N = 1e6+10;
using namespace std;
typedef long long LL;

LL t,n;

vector<LL> a;

void solve(LL n,LL primes)
{
    while(n%2==0)
    {
        n/=2;
        primes*=2;
    }
    while(n%3==0)
    {
        n/=3;
        primes*=3;
    }
    if(n==1) 
    {
        a.push_back(n*primes);
    }
    else
    {
        LL res=1;
        while(3*res<n)
        {
            res*=3;
        }
        a.push_back(res*primes);
        LL y=n-res;
        solve(y,primes);
    }
}

int main()
{
    freopen("distribution.in","r",stdin);
	freopen("distribution.out", "w", stdout);
    cin>>t;
    while(t--)
    {
        a.clear();
        cin>>n;
        solve(n,1);
        //sort(a.begin(),a.end());
        cout<<a.size()<<endl;
        for(LL i=0;i<a.size();i++)
        {
            cout<<a[i]<<' ';
        }
        cout<<endl;
    }
    
}

E - Easy Arithmetic

题意:通过在给定字符串的合法位置增加加号或减号,使得计算结果最大。
在这里插入图片描述
思路:这道模拟抓住一个之后性质就非常好做,不发现性质会有些繁琐。我们发现这道题的难点就是对减号的处理。
对于这样一个样例:
在这里插入图片描述

很明显5之前的0都要加号隔开,5之后的零都要保留。简单来说就是减号后边的第二个非零数字之后的所有字符都不能隔开。比如这个 − 31078 -31078 31078中1后边的数都不用加号隔开,这个5之后的数也不用加号隔开。有了这个性质代码长度就很短了。

#include <iostream>
#include <cstdio>
#include <cstring>

using namespace std;

int main()
{
    freopen("easy.in","r",stdin);
    freopen("easy.out","w",stdout);
    string s;
    cin >> s;
    int state = 0;
    for(int i = 0 ; i < s.size() ; i++)
    {
        if(s[i] == '-') state = 2;
        if(s[i] == '+') state = 1;
        if(state == 2 && s[i] == '0' && s[i - 1] != '-')
        {
            printf("+%c",s[i]);
        }
        else if(state == 2 && s[i - 1] != '+' && s[i - 1] != '-' && s[i] >= '1' && s[i] <= '9')
        {
            printf("+%c",s[i]);
            state = 1;
        }
        else printf("%c",s[i]);
    }
    printf("\n");
    return 0;
}

H - Hash Code Hacker

题意:这道题题意就是个一个数 n n n ,让你输出n中 p=31 的哈希值相等的任意字符串。
思路:根据哈希值的定义,如果使得前一位-1,后一位加31就可以使得哈希值不变,所以假设一开始的数都是 ‘X’ ,通过变化相邻两位的哈希值 n n n次得到 n n n 个不同的等哈希值的串。

#include<iostream>
using namespace std;
char a[1010];
int main()
{
	freopen("hash.in","r",stdin);
	freopen("hash.out", "w", stdout);
	int n;
	cin>>n;
	for(int i=0;i<n;i++)
	{
		a[i]='X';
		cout<<a[i];
	}
	cout<<endl;
	for(int i=0;i+1<n;i++)
	{
		a[i]-=1;
		a[i+1]+=31;
		cout<<a<<endl;
	}
	return 0;
}

Problem L. Lucky Chances

题意简单的模拟
思路:枚举。

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
int g[110][110];
typedef long long LL;
int n,m;
int check(int x,int y)
{
    int cnt=0;
    for(int i=1;i<=x;i++)
    {
        if(g[i][y]>=g[x][y]&&i!=x) break;
        if(i==x) cnt++;
    }
    for(int i=x;i<=n;i++)
    {
        if(g[i][y]>=g[x][y]&&i!=x) break;
        if(i==n) cnt++;
    }
    for(int i=1;i<=y;i++)
    {
        if(g[x][i]>=g[x][y]&&i!=y) break;
        if(i==y) cnt++;
    }
    for(int i=y;i<=m;i++)
    {
        if(g[x][i]>=g[x][y]&&i!=y) break;
        if(i==m) cnt++;
    }
    return cnt;
}
int main()
{
    freopen("lucky.in","r",stdin);
    freopen("lucky.out","w",stdout);
    cin>>n>>m;
    for(int i=1;i<=n;i++)
    {
        for(int j=1;j<=m;j++)
        {
            cin>>g[i][j];
        }
    }
    LL res=0;
    for(int i=1;i<=n;i++)
    {
        for(int j=1;j<=m;j++)
        {
            res+=check(i,j);
           // cout<<i<<' '<<j<<' '<<check(i,j)<<endl;
        }
    }
    cout<<res<<endl;
}
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值