131017试题 – 解题报告
A
a.cpp/c/pas a.in a.out
1s64MB
求n个数的最小公倍数
输入
第一行一个数n
第二行有n个数,每两个数直接用空格隔开
样例
输入
3
4 5 6
输出
60
数据范围
对于30% n<=2 ai<=10000
对于60% 答案小于10^18
对于100% n<=100 ai<=10^9
首先,思想很简单,分解质因数即可,如
26= 21 + 30 + 50 + 70 + 110+131
36= 22 + 32 + 50 + 70 + 110+ 130
那么显然lcm就为21 + 32 + 131
那么打一个素数表就可以解决了
值得注意的是,唯一分解定律中,如果1~sqrt(n)中分解完了,最后剩下的数不为1的话,那么他也是一个质数,所以千万别把这一个质数忘了
还有,记得写高精乘法,不过我的高精好像写跪了,正在调试中……
(代码就算了)
B
b.cpp/c/pas b.in b.out1s 64MB
题目描述
给定一个长度为n的数列,每个数可以加上或者减去小于等于X的数,使数列变成严格递增的正整数数列,最小的X
输入格式
第一行包含一个数n,表示数列中数的个数
第二行包含n个用空格隔开的数ai
输出格式
一行,包括一个数表示最小的X
输入样例
3
1 2 2
输出样例
1
数据范围
30%的数据满足n=5,0<=ai<=10
60%的数据满足n=1000,0<=ai<=1000
100%的数据满足n=10^5,0<=ai<=10^9
很简单的二分,二分出题目要求的最小x,然后验证即可
验证的时候可以贪心,可以想想,如果当前这个数比上一个数大,那么肯定把他减小(并且在合法范围内减得越小越好!这样才能给后面留出更多的改变空间,换句话说,这样才能保证最优)
如果当前比上一个数小,那么不用说可定要加上去,如果+x还比上一个小(或者相等),那么果断返回false(也就是当前二分出来的值肯定不满足题意),如果比上一个数大了,我们就把它加到 (上一个数+1) 值 (同理,这样也是为了让后面变得更优)
还有就是XJ坑,明明是正整数序列,非要告诉我们最后可以有0(题目意思是最后加减出来的序列必须是正整数),好多人都是这里错了,找了好久的错……
可以看看代码
#include <cstdio>//By Jiangzh
const int N = 100000 + 10;
typedef long long LL;
int n;
LL a[N];
bool check(LL mid)
{
LL last = 0;
for(int i = 1; i <= n; i++)
{
if(a[i] > last)
{
int t = a[i] - last - 1;
// a[i] - t = last + 1
if(t > mid) t = mid;
last = a[i] - t;
}
else {
if(a[i] + mid <= last) return 0;
int t = last - a[i] + 1;
last = a[i] + t;
}
}
return 1;
}
void solve()
{
LL L = 0, R = 0x3f3f3f3f3f3f3f3fll;
while(L < R)
{
LL mid = L + (R - L) / 2;
if(check(mid)) R = mid;
else L = mid + 1;
}
printf("%I64d\n", R);
}
int main()
{
freopen("b.in", "r", stdin);
freopen("b.out", "w", stdout);
scanf("%d", &n);
for(int i = 1; i <= n; i++) scanf("%d", &a[i]);
solve();
return 0;
}
C
c.cpp/c/pas c.in c.out1s 64MB
一个01串 想要将该串切成几段 使得每段对应的十进制值都是5^i的形式 即5^0,5^1,5^2....
问最小需要切成几段 注意每段不能有前导0 比如01是不合法
输入
一行,即一个01串
输出
一行,即为答案
样例
输入
1101
输出
2
数据范围
30% n<=10
60% n<=20
100% n<=50
标解是dp,但是dfs其实也可以轻松过
dfs的过程不用说了吧,找到合法的位置,然后枚举是否在这里截断(千万不能贪心找最后一个能截断的位置,因为会影响其他的分配)
看似2^n的复杂度,但是题目很特殊,找最小值,直接加一个最优化剪枝,轻松过
C++ AC Code
#include <cstdio>// By Jiangzh
#include <cstring>
#include <algorithm>
using std::min;
char s[100];
int len;
int Five = 0x3f3f3f3f;
bool check(long long sum)
{
if(sum % 5 != 0) return 0;
while(sum > 1 && sum % 5 == 0)
{
sum /= 5;
}
return sum == 1;
}
void dfs(int le, int times)
{
if(times >= Five) return;
//printf("%d %d\n", le, times);
if(le == 0) {Five = min(Five, times); return;}
int k = 1; long long sum = 0;
for(int i = le - 1; i >= 0; i--, k *= 2)
{
int c = s[i] - '0';
sum += (long long)c * k;
if((sum == 1 || check(sum)) && c != 0) dfs(i, times+1);
}
}
int main()
{
freopen("c.in", "r", stdin);
freopen("c.out", "w", stdout);
scanf("%s", s); len = strlen(s);
dfs(len, 0);
if(Five == 0x3f3f3f3f) printf("-1\n");
else printf("%d\n", Five);
return 0;
}
131018试题 - 解题报告
Xor
xor.c/cpp/pas
1s 64M
描述
求1到n的异或值,即1^2^3.....^n
输入(xor.in)
一个数n
输出(xor.out)
一个数,表示所求的值
样例
输入
3
输出
0
数据范围
50% 1<=n<=10^6
100% 1<=n<=10^18
看看范围,10^18,出了O(1)的算法还能怎么做?
就算不知道也该写50分的朴素吧,全部打出来也应该能发现规律的
具体规律自己找找吧
C++ AC Code
#include <cstdio>// By Jiangzh
long long n;
int main()
{
freopen("xor.in", "r", stdin);
freopen("xor.out", "w", stdout);
scanf("%I64d", &n);
long long num = n / 4 * 4;
n %= 4;
long long ans = num;
for(int i = 1; i <= n; i++)
ans ^= (long long)(i + num);
printf("%I64d\n", ans);
return 0;
}
Rooks
rooks.c/cpp/pas
2S/64M
描述
N*N的国际象棋棋盘上有K 个车,第i 个车位于第Ri 行,第Ci 列。求至少被一个车攻击的格子数量
输入(rooks.in)
第1 行,2 个整数N,K。
接下来K 行,每行2 个整数Ri,Ci。
输出(rooks.out)
1 个整数,表示被攻击的格子数量。
样例
输入
3 2
1 2
2 2
输出
7
数据范围
对于30% 的数据,1<=N<=1000; 1<=K<=1000
对于60% 的数据,1<=N<=1000000; 1<=K<=1000000
对于100% 的数据,1<=N<=1000000000; 1<=K<=1000000; 1<=Ri,Ci<=N
先吐槽一下cena,用 set 凭什么超时啊!!!
言归正传,看看怎么做(下面是样例)
再看看一个数据
观察一下没有攻击到的格子,刚好是可以重新组成一个矩形的!(很容易证明)
那么我们只需要知道有多少行没有车,有多少列没有车,乘起来就是不会被攻击到的格子,在用总数减去即可
给出两份代码,一个是用set会T掉的,一个是快排
C++ Code // STL set
#include <cstdio>// By Jiangzh
#include <set>
using std::set;
const int N = 1000000 + 10;
int n, m;
set<int> R, C;
int main()
{
freopen("rooks.in", "r", stdin);
freopen("rooks.out", "w", stdout);
scanf("%d%d", &n, &m);
int hang, lie;
hang = lie = n;
for(int i = 1; i <= m; i++)
{
int a, b; scanf("%d%d", &a, &b);
if(!R.count(a))
{
R.insert(a);
hang--;
}
if(!C.count(b))
{
C.insert(b);
lie--;
}
}
printf("%I64d\n", (long long)n*n - (long long)hang*lie);
return 0;
}
C++ AC Code // sort
#include <cstdio>// By Jiangzh
#include <algorithm>
using std::sort;
const int N = 1000000 + 10;
int n, m;
int a[N], b[N];
void work(int *c, int &num)
{
int last = 0;
for(int i = 1; i <= m; i++)
{
if(c[i]!=last) num--;
last = c[i];
}
}
int main()
{
freopen("rooks.in", "r", stdin);
freopen("rooks.out", "w", stdout);
scanf("%d%d", &n, &m);
int hang, lie;
hang = lie = n;
for(int i = 1; i <= m; i++)
{
scanf("%d%d", &a[i], &b[i]);
}
sort(a+1, a+1+m);
sort(b+1, b+1+m);
work(a, hang);
work(b, lie);
printf("%I64d\n", (long long)n*n - (long long)hang*lie);
return 0;
}
Howmany colorings
color.c/cpp/pas
1S/256M
描述
有一颗N 个节点的树,节点用1, 2.....N 编号。你要给它染色,使得相邻节点的颜色不同。有M 种颜色,
用1,2.....M 编号。每个节点可以染M 种颜色中的若干种,求不同染色方案的数量除以(10^9 + 7)的余数。
输入(color.in)
第1 行,2 个整数N,M
接下来N 行,第i 行表示节点i 可以染的颜色。第1个整数ki,表示可以染的颜色数量。接下来ki个
整数,表示可以染的颜色编号。
最后N-1 行,每行2个整数Ai,Bi表示边(Ai,Bi).
输出(color.out)
一行,即答案
样例
输入
2 2
1 1
2 1 2
1 2
输出
1
数据范围
对于30% 的数据, 1<=N<=10; 1<=M<=4;
对于60% 的数据, 1<=N<=200; 1<=M<=200;
对于100% 的数据,1<=N<=5000;1<=M<=5000 1<=ki 所有ki的和<=1000000
一道比较常规的树形dp,其实也不是树形dp啦,就是很普通的dp,记忆化搜索即可
f[x][c]表示以节点x为根,并且把x涂成颜色c,的方案数
至于怎么转移,也不难,乘法原理,加法原理。每个点选颜色的时候加,各个点之间乘
不过内存似乎有点吃紧,正在修改中……