南昌大学航天杯第二届程序设计竞赛校赛网络同步赛 - 题解

A - ID and password

题目描述 

Users prefer simple passwords that are easy to remember, but such passwords are often insecure. Some sites use random computer-generated passwords, but users have a hard time remembering them. But today I improved the website system, which greatly reduced the probability of password being cracked. So we can generate a simple password for each ID by default.
Each id has its initial password.
Do the following two jobs at the same time:
1.Turn uppercase letters to lowercase letters
2.Turn lowercase letters to uppercase letters
You can get the initial password for the id.

输入描述:

The input consists of one or more identities(ID), one per line.
The length of each id is between 1 and 100. Each id only consists of letters and digits.

输出描述:

For each id, output the corresponding initial password.
示例1

输入

JDJhadjsazA
ksfjkkdSDJ23

输出

jdjHADJSAZa
KSFJKKDsdj23
#include<bits/stdc++.h>
using namespace std;
string str;
int main()
{
    while(cin>>str)
    {
        int len = str.length();
        for(int i=0;i<len;i++) {
            if(str[i] >= 'a' && str[i] <= 'z') str[i] = 'A' + str[i] - 'a';
            else if(str[i] >= 'A' && str[i] <= 'Z') str[i] = 'a' + str[i] - 'A';
        }
        cout<<str<<endl;
    }
    return 0;
}


B-取石子

题目描述 

现在有两堆石子,两个人轮流从中取石子,且每个人每一次只能取1、3或9个石子,取到最后一个石子的人win。
假设先手后手都会选择最好的方式来取石子,请您判断先后手的输赢情况。

输入描述:

多组输入
每组一行,一行包括两个正整数n1和n2(1<=n1<=100,1<=n2<=100),代表了两堆石子的数目

输出描述:

如果先手能赢,输出"win";否则就输出"lose"。
示例1

输入

1 1 
1 2

输出

lose
win

正解: SG 函数模板题

实际上也有规律 判断 n1+n2 的奇偶即可

#include<bits/stdc++.h>
using namespace std;
int main()
{
    int n1,n2;
    while(~scanf("%d%d",&n1,&n2))
    {
        int tot = n1 + n2;
        if(tot & 1) printf("win\n");
        else printf("lose\n");
    }
    return 0;
}

C-水题

题目描述 

一张地图上有有N个城市,他们可以通过双向道路互相连接,但是每两座城市只能有一条双向道路互相连接。

现在我们想要满足条件“地图中不能有任意三个城市可以互相直达”,请问满足这个条件的最大道路数是多少?

输入描述:

多组输入

每组输入一个N(1<=N<=1000)

输出描述:

每组答案输出一行
示例1

输入

4
2
3

输出

4
1
2

官方题解

地图中不能有任意三个城市可以互相直达。其实就把所有城市分成两组,组
内的不能相互连接,组间的可以随便连接。那么最大道路数就是第一组内城市数
量*第二组内城市数量。城市总数已经确定,乘积最大只要让两组城市数量尽可
能相近。

那么答案就是n/2*(n-n/2)

居然没想到...还是菜

#include<bits/stdc++.h>
using namespace std;
int main()
{
    int n;
    while(~scanf("%d",&n))
    {
        printf("%d\n",n/2 * (n-n/2));
    }
    return 0;
}

D-小C的记事本

题目描述 

小C最近学会了java小程序的开发,他很开心,于是想做一个简单的记事本程序练练手。

他希望他的记事本包含以下功能:

1、append(str),向记事本插入字符串 str(英文字符)

2、delete(k),删除记事本最后k个字符(保证不为空串)

3、print(k),输出记事本第k个字符(保证不为空串)

4、undo(),撤销最近的1(或者)操作,使记事本回到1(或者2)操作之前的状态

可怜的小C琢磨了半天还是做不来,聪明的你能解决小C的问题吗?

输入描述:

多组输入

第一行输入一个整数q,代表操作总数

以下q行每行描述了一个操作,每行以一个整数t开始(1 <= t <= 4)。

t表示上述问题陈述中定义的操作类型。 如果操作需要参数,则后跟空格分隔的参数。

题目保证所有操作均合法

1 <= q <= 10^6 
1 <= k <= |记事本内容长度| 
每个测试数据中str的总长度 <= 10^6

请使用 ios::sync_with_stdio(false); 对读写进行加速

输出描述:

所有操作类型3必须输出第k个字符,每行以换行符结束。
示例1

输入

8
1 ab
3 2
2 2
1 cd
3 1
4
4
3 1

输出

b
c
a

说明

**样例解释**

假设记事本用字符串S表示

1、插入ab,S="ab"

2、输出第2个字符,是b

3、删除最后2个字符,S=""

4、插入cd, S="cd"

5、输出第1个字符,是c

6、撤销,此时S=""

7、撤销,此时S="ab"

8、输出第1个字符,是a

堆栈模拟

#include<bits/stdc++.h>
using namespace std;
string node,str;

stack<string> sta;
int main()
{
    ios::sync_with_stdio(false);
    int n;
    while(cin>>n)
    {
        while(!sta.empty()) sta.pop();
        node.clear();
        int op,las,k;
        for(int i=0;i<n;i++) {
            cin>>op;
            if(op == 1) {
                cin>>str;
                sta.push(node);
                node += str;
            }
            else if(op == 2) {
                cin>>k;
                sta.push(node);
                node.erase(node.length()-k,k);
            }
            else if(op == 3) {
                cin>>k;
                cout<<node[k-1]<<endl;
            }
            else {
                if(!sta.empty()) {
                    node = sta.top();
                    sta.pop();
                }
            }
        }
    }
}

E-小C的周末

题目描述 

愉快的周末到了,小C和他的N-1个朋友买了M个游戏,游戏编号从1~M。每个游戏都是多人游戏,他们打算周末一起打游戏。

小C的每个朋友都决定好了要玩哪一款游戏(会有一组人打同一款游戏),并且每人手上都有一台游戏机,这种游戏机可以通过特定的游戏机连接线连接起来。

但是,他们面临着一个问题:目前没有一个朋友的游戏机是互相连接的。所以它们必须用可用的游戏机连接线连接起来。小C决定依次使用第 i 条连接线把他的朋友 ui 和 vi 的游戏机连接起来。也就是说,假设有Q条连接线,小C只能先使用第一条,然后使用第二条,然后使用第三条。。。最后使用第Q条。

一个游戏能开始的条件是所有玩这个游戏的朋友的游戏机都被连接起来(如果不是直接连接的话,那么就必须存在一条连接它们的路径)。他们希望尽快开始比赛。

在每个游戏中,找出在添加了第几条连接线之后能开始游戏。如果在一个游戏中只有一个人玩,则输出0(因为他立马可以开始游戏)。如果不存在,则输出-1

输入描述:

多组输入

第一行包含三个整数N,M,Q。

第二行给N个用空格分隔的整数,第 i 个整数代表第 i 个朋友想玩的游戏。

接下来的Q行,每行两个整数(u, v),代表电线 i 连接的两个人的电脑

1 <= N, M <= 10^5
0 <= Q <= 10^5

输出描述:

对于每个游戏,输出一个整数,表示添加了第几条连接线之后能开始游戏,每行以换行符结束
示例1

输入

5 2 4
1 2 2 2 1
1 2 
2 3
1 5
4 5

输出

3
4

说明

**样例解释**

第一个游戏有两个人参加(1,5),在添加了第三条电线之后他们电脑互相连接

第二个游戏三个人参加(2, 3, 4),在添加第四条电线之后他们电脑互相连接

挖个坑:

题目要求他们希望尽快开始比赛,因此要输出一个最小的连接数。此时应考
虑二分,让我们来考虑只有一个游戏 c 的情况。
如何在O(|Vc|)时间内判断游戏c是否联通?我们维护一个并查集的数据结
构就可以做到,在这里只需要判断顶点Vc[i]与顶点Vc[i-1]的根结点是否相同即
可。然后我们二分第 t 根连接线并做模拟,判断连接到第 t 根连接线的时候游
戏 c 是否联通。(模拟就是二分到第 t 根时添加 t 根线到并查集中)。
时间复杂度为O(Q+|Vc|)logQ
在这个问题中,我们需要二分所有的游戏 c。这需要维护一个区间[l, r]来做
二分。这个区间是我们做二分的时候用到的,区间的变化为[l, m]或者[m+1, r],
m=(l+r)/2。对于所有的游戏 c ,那么我们如何并行维护区间[li, ri]呢?
首先我们是需要二分Q条连接线logQ次,每次做一次模拟。
在模拟添加到第 i 根连接线中,需要检查是否存在某个游戏 c ,使得(lc +
rc) / 2 == i。如果存在,则需要对区间[li, ri]作出改变。对于每次模拟我们需要
O(Q+\sum{|Vc|})次操作,也就是等于O(Q+N),并且我们只做了log(Q)次模拟,

因此总的时间复杂度为O(Q+N)logQ

以上是整体二分吗?没想出来怎么二分.


写了一个合并并茶几的算法,感觉应该会TLE,MLE吧.居然没有,可能数据太弱

因为n和m都是 1e5,这个map开二维是会MLE的吧

还有q是1e5,每次合并都需要o(m) 总时间是O(m*q) 也是会TLE吧

#include<bits/stdc++.h>
using namespace std;
const int maxn = 1e5+10;
int fnt[maxn],urank[maxn];
int cnt[maxn],belong[maxn],ans[maxn];
map<int,int> ma[maxn]; /// 记录下第i个位置上的所有游戏的已经连接的个数,初始化为1
struct DSU {
    void makeset(int x) {
        urank[fnt[x]=x] = 0;
    }
    void init() {
        for(int i=0;i<maxn;i++) urank[fnt[i]=i] = 0;
    }
    int ufind(int x) {
        if(fnt[x] != x) fnt[x] = ufind(fnt[x]);
        return fnt[x];
    }
    void umerge(int x,int y,int l) {
        x = ufind(x);
        y = ufind(y);
        if(x == y) return;
        if(ma[x].size() > ma[y].size()) swap(x,y);
        fnt[x] = y;
        for(auto v:ma[x]) {
            ma[y][v.first] += v.second;
            if(ans[v.first] == -1 && ma[y][v.first] == cnt[v.first]) ans[v.first] = l;
        }
    }
}dsu;
int main()
{
    int n,m,q;
    while(~scanf("%d%d%d",&n,&m,&q))
    {
        for(int i=0;i<maxn;i++) ma[i].clear();
        memset(cnt,0,sizeof(cnt));
        memset(ans,-1,sizeof(ans));
        dsu.init();
        for(int i=1;i<=n;i++) scanf("%d",&belong[i]),cnt[belong[i]]++,ma[i][belong[i]]++;
        for(int i=1;i<=m;i++) if(cnt[i] == 1) ans[i] = 0;
        for(int i=1,u,v;i<=q;i++) {
            scanf("%d%d",&u,&v);
            dsu.umerge(u,v,i);
        }
        for(int i=1;i<=m;i++) printf("%d\n",ans[i]);
    }
    return 0;
}

F-小汤的疑惑

题目描述 

阿汤同学最近刚学数论,他发现数论实在是太有趣了,于是他想让你也感受一下数论的乐趣。现在他给你一个正整数 N 和一个正整数 M,要求你用 N 对 M 进行取余操作,即 N % M,记余数为 S。
但是他发现这样好像并不能让你感受到数论的乐趣,于是他想让你在N 对 M 取余操作的基础上再求出这个余数 S 能分解出多少个不同质因数。

质因数:质因数在数论里是指能整除给定正整数的质数,质数就是只能整除 1 和本身的数,定义 2 是最小的质数。

输入描述:

从标准输入读入数据。
输入包含多组数据,第一行一个整数 T 代表数据组数。接下来依
次描述每组数据,对于每组数据:
第一行输入正整数 N,第二行输入正整数 M

【数据规模】
1≤N≤10^100
1≤M≤2^31-1

输出描述:

输出到标准输出。
对于每组数据,输出一行:
余数 S 能分解出的不同质因数的个数。
示例1

输入

2
68
40
6
180

输出

2
2
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
string str;
ll mod;
ll solve(string str,ll mod) {
    ll ans = 0;
    int len = str.length();
    for(int i=0;i<len;i++) {
        ans = (ans*10+str[i]-'0') % mod;
    }
    return ans;
}
int main()
{
    int caset;cin>>caset;
    while(caset--) {
        cin>>str;
        cin>>mod;
        ll ans = solve(str,mod);
        // printf("%lld\n",ans);
        ll cnt = 0;
        for(int i=2;i<=sqrt(ans);i++) {
            if(ans % i == 0) {
                cnt++;
                while(ans % i == 0) ans /= i;
            }
        }
        if(ans > 1) cnt++;
        printf("%lld\n",cnt);
    }
    return 0;
}


G-阿汤的数组

题目描述 

阿汤同学为了准备下学期的 ACM-ICPC,刷了很多的题目,他觉得自己已经比较厉害了,于是想出个题目考考你。现在他给你一个数组 A,问你是否能将该数组划分成数组 B、C 使得 B 数组的平均数和C 数组的平均数相等,数组 B 和 C 都不能为空。

输入描述:

从标准输入读入数据。
输入包含多组数据,第一行一个整数 T 代表数据组数。接下来依次描述每组数据,对于每组数据:
第一行输入正整数 N,第二行输入 N 个非负整数

1≤|A|≤30 (数组 A 的长度范围在 1 到 30 之间 )
0≤A[i]≤10000 (数组 A 中的元素)

输出描述:

输出到标准输出。
对于每组数据,输出一行:
如果能划分成满足题目要求的数组 B 和 C 则输出 yes,否则输出no
示例1

输入

1
8
1 2 3 4 5 6 7 8

输出

yes

说明

样例说明:将数组 A 划分成【1,4,5,8】和【2,3,6,7】,平均数为 4.5

官方题解:

设数组A的和为SumA,长度为N,B的和为SumB,长度为M

则需满足条件:
(SumA-SumB) / (N-M) = sumB / M;即可得到数组B 和数组A的平均数相等,所以我们可以对数组A中的元素减
去数组A的平均值,问题转化为在数组A中找到一个数组B使得数组B的和为0.显
然的做法是枚举A中数字的所有可能的组合,但是因为A的长度多达30, 2 30 会超
时,所以考虑拆分成两个部分,每一边最多15个元素,复杂度变成 2 15 ,然后二

分在两边找答案就可以了。

再挖个坑,没想出来怎么二分,二分出来的两边怎么合并?


dp做法:

想到于01背包,找到A中的一个子集B使得 sumb / m == sum / n

#include<bits/stdc++.h>
using namespace std;
double arr[35];
double sum;
int n;
bool dp[3100000][35];
bool solve() 
{
    memset(dp,0,sizeof(dp));
    dp[0][0] = true;
    int smax = 0;
    for(int i=1;i<=n;i++) {
        for(int j=smax;j>=0;j--) {
            for(int z=0;z<n-1;z++) {
                if(dp[j][z])
                {
                    int k = j + arr[i];
                    dp[k][z+1] = true;
                    smax = max(smax,k);
                    if(fabs(k*1.0/(z+1)-sum) <= 0.0000000001) return 1;

                }
            }
        }
    }
    return false;
}
int main()
{
    int caset;scanf("%d",&caset);
    while(caset--)
    {
        scanf("%d",&n);
        sum = 0;
        for(int i=0;i<n;i++) scanf("%lf",&arr[i]),sum += arr[i];
        sum /= n;
        if(solve()) printf("yes\n");
        else printf("no\n");
    }
    return 0;
}

H-小q的数列

题目描述 

小q最近迷上了各种好玩的数列,这天,他发现了一个有趣的数列,其递推公式如下:

f[0]=0 f[1]=1;
f[i]=f[i/2]+f[i%2];(i>=2)

现在,他想考考你,问:给你一个n,代表数列的第n项,你能不能马上说出f[n]的值是多少,以及f[n]所代表的值第一次出现在数列的哪一项中?(这里的意思是:可以发现这个数列里某几项的值是可能相等的,则存在这样一个关系f[n'] = f[n] = f[x/2]+f[x%2] = f[x]...(n'<n<x) 他们的值都相等,这里需要你输出最小的那个n'的值)(n<10^18)

输入描述:

输入第一行一个t
随后t行,每行一个数n,代表你需要求数列的第n项,和相应的n'
(t<4*10^5)

输出描述:

输出每行两个正整数
f[n]和n',以空格分隔
示例1

输入

2
0
1

输出

0 0
1 1


官方题解:

可以说是一道规律题,我们看这个数列 f[i]=f[i/2]+f[i%2],略做变换:
有f[i]=f[i/2](i为偶数时) f[i]=f[i/2]+1(i为奇数时),其本质是一个数的
二进制表示下,有几个1,所以我们在求某一项时,只需要不断,右移即可,同时
判断一下新得到的数字跟1相与是否为1,是的话答案就加+1,直到这个数为0
即可。至于n’的求法,根据上面的结论,第一次出现的必为全是1的那种情况,
算一下2的f[n]次方减1即可。根据二进制的特性,可能会出现重复询问2的某次

方的情况,存一下结果可以优化程序,当然不优化也能过。

代码:

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
ll solve(ll n) {
    if( n <= 1 ) return n;
    if(n & 1) return solve(n/2) + 1;
    return solve(n/2);     
}
ll power(ll a,ll b) {
    ll ans = 1;
    while(b) {
        if(b&1) ans = ans * a;
        a = a * a;
        b >>= 1;
    }
    return ans;
}
int main()
{
    ll n;
    int caset;scanf("%d",&caset);
    while(caset--) {
        scanf("%lld",&n);
        ll fn = solve(n);
        printf("%lld %lld\n",fn,power(2,fn)-1);
    }
    return 0;
}

I-小q的时钟

题目描述 

小q最近在做一个项目,其中涉及到了一个计时器的使用,但是笨笨的小q却犯难了,他想请你帮助他完成这一部分的代码实现。

输入描述:

多组输入
他将给你两个整数 begin 和 end ,代表时间的开始和结束(秒为单位)(0<=begin<end<=10^18)

输出描述:

请你输出时间流逝了多少
在一行中输出流逝的时间。 以hh:mm:ss 两位数字的格式输出(小时若不足两位以两位输出,若超过两位则按实际输出)
示例1

输入

0 3600
0 1000000000000000000

输出

01:00:00
277777777777777:46:40

代码:

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
int main()
{
    ll be,en;
    while(~scanf("%lld%lld",&be,&en))
    {
        ll tot = en - be;
        ll sec = tot % 60;tot /= 60;
        ll min = tot % 60;tot /= 60;
        ll hour = tot;
        printf("%02lld:%02lld:%02lld\n",hour,min,sec);
    }
    return 0;
}

猜你喜欢

转载自blog.csdn.net/m0_38013346/article/details/80396901