解题报告(18.06.02)
选拔考试
T1:
小K手中有n张牌,每张牌上有一个一位数的数,这个字数不是0就是5。小K从这些牌在抽出任意张(不能抽0张),排成一行这样就组成了一个数。使得这个数尽可能大,而且可以被90整除。
样例:
Input:
4
5 0 5 0
Output:
0
思路:
被90整除即同时被9和10整除,被10整除则至少有一个0,被9整除则5的个数为9的倍数,简单题
直接上代码:
#include<cstdio>
#include<cstring>
#include<iostream>
#define CL(X,N) memset(X, N, sizeof(X))
#define LL long long
using namespace std;
const int maxn=1e3+10;
int a[maxn], n;
int cnt5=0, cnt0=0;
inline void _init() {
freopen("zaf.in", "r", stdin);
freopen("zaf.out", "w", stdout);
}
int main() {
_init();
scanf("%d", &n);
for(int i=0; i<n; i++) {
scanf("%d", a+i);
if(a[i]==5) cnt5++;
if(a[i]==0) cnt0++;
}
if(!cnt0) {
printf("-1");
return 0;
}
int ls=cnt5/9;
if(ls){
for(int i=0; i<ls*9; i++) printf("5");
for(int i=0; i<cnt0; i++) printf("0");
} else printf("0");
return 0;
}
这么简单,不想打注释
T2:
现在有一块玻璃,是长方形的(w 毫米× h 毫米),现在要对他进行切割。切割的方向有两种,横向和纵向。每一次切割之后就会有若干块玻璃被分成两块更小的玻璃。在切割之后玻璃不会被移动。
现在想知道每次切割之后面积最大的一块玻璃是多少。
样例:
Input:
4 3 4
H 2
V 2
V 3
V 1
Output:
8
4
4
2
样例解释:
对于第四次切割,下面四块玻璃的面积是一样大的。都是2
思路:
标准思路:倒过来处理,如果当前位置x是割线,那么 H[x].l表示该割线左面那条割线的位置, H[x].r表示该割线右面那条割线的位置, H[i].val表示该割线与左面那条割线之间的长度, 这样每次增加割线倒过来之后就相当于删除割线, 当然每次删除只要O(1)更新这条割线左右两边割线的值就好,每次答案就是max(H[i].val)*max(V[i].val),i∈[0,w(h)] ,总复杂度:O(n)
当时思路:
为了避免TLE,先将操作存下来,并标记先后(将上下左右边界也存进去,id设为0),按位置排序后,正向跑for并记录最大的当前宽度和高度,依次输出结果(其实差不多)
燃鹅,令我懵逼的是,有人每读入一个操作就sort一次,然后跑for,居然也没TLE(一定是数据太水)
然后放代码吧:
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
#define CL(X,N) memset(X, N, sizeof(X))
#define LL long long
using namespace std;
const int maxn=2e5+10;
int w, h, n;
struct cut {
LL x;
int id;
cut(){}
cut(LL _x, int _id) : x(_x), id(_id) {}
}wl[maxn], hl[maxn];
bool cmp(cut a, cut b) {
return a.x<b.x;
}
inline void _init() {
freopen("cut.in", "r", stdin);
freopen("cut.out", "w", stdout);
}
int cnth=2, cntw=2;
int main() {
_init();
scanf("%d%d%d", &w, &h, &n);
wl[0]=cut(0, 0);
wl[1]=cut(w, 0);
hl[0]=cut(0, 0);
hl[1]=cut(h, 0); //初始化
for(int i=1; i<=n; i++) {
char cmd[2];
LL x;
scanf("%s", cmd); //把'\n'吞掉
scanf("%d", &x);
if(cmd[0]=='H') {
hl[cnth++]=cut(x, i);
} else if(cmd[0]=='V') {
wl[cntw++]=cut(x, i);
}
}
sort(wl, wl+cntw, cmp);
sort(hl, hl+cnth, cmp);
for(int i=1; i<=n; i++) {
LL answ=0, ansh=0;
LL lastw=0, lasth=0;
for(int j=1; j<cntw; j++) {
if(wl[j].id>i) continue;
answ=max(answ, wl[j].x-lastw); //计算最大宽度
lastw=wl[j].x;
}
for(int j=1; j<cnth; j++) {
if(hl[j].id>i) continue;
ansh=max(ansh, hl[j].x-lasth); //最大高度
lasth=hl[j].x;
}
printf("%lld\n", answ*ansh);
}
return 0;
}
标程:
#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
const int MAXN = 2e5 + 10;
int maxW, maxH;
int preW[MAXN], preH[MAXN];
int rankW[MAXN], rankH[MAXN];
int visW[MAXN], visH[MAXN];
int w, h, n;
char opt[3];
int op[MAXN];
int num[MAXN];
long long ans[MAXN];
inline void scan_d(int &ret)
{
char c;
ret = 0;
while ((c = getchar()) < '0' || c > '9');
while (c >= '0' && c <= '9')
{
ret = ret * 10 + (c - '0'), c = getchar();
}
}
int findW(int x)
{
if (preW[x] != x)
{
preW[x] = findW(preW[x]);
}
return preW[x];
}
void joinW(int x, int y)
{
x = findW(x);
y = findW(y);
if (x == y)
{
return ;
}
preW[y] = x;
rankW[x] += rankW[y];
maxW = max(rankW[x], maxW);
}
int findH(int x)
{
if (preH[x] != x)
{
preH[x] = findH(preH[x]);
}
return preH[x];
}
void joinH(int x, int y)
{
x = findH(x);
y = findH(y);
if (x == y)
{
return ;
}
preH[y] = x;
rankH[x] += rankH[y];
maxH = max(rankH[x], maxH);
}
void init()
{
for (int i = 0; i <= w; i++)
{
preW[i] = i;
rankW[i] = 1;
}
for (int i = 0; i <= h; i++)
{
preH[i] = i;
rankH[i] = 1;
}
memset(visW, 0, sizeof(visW));
memset(visH, 0, sizeof(visH));
memset(op, 0, sizeof(op));
}
int main()
{
freopen("cut.in","r",stdin);
freopen("cut.out","w",stdout);
cin >> w >> h >> n;
init();
rankW[0] = rankH[0] = 0;
for (int i = 0; i < n; i++)
{
scanf("%s", opt);
scan_d(num[i]);
if (opt[0] == 'H')
{
op[i] = 1;
visH[num[i]] = 1;
}
else
{
visW[num[i]] = 1;
}
}
maxH = 1;
maxW = 1;
for (int i = 1; i < w; i++)
{
if (!visW[i])
{
joinW(i, i + 1);
}
}
for (int i = 1; i < h; i++)
{
if (!visH[i])
{
joinH(i, i + 1);
}
}
for (int i = n - 1; i >= 0; i--)
{
ans[i] = (long long)(maxH) * (maxW);
if (op[i])
{
joinH(num[i], num[i] + 1);
}
else
{
joinW(num[i], num[i] + 1);
}
}
for (int i = 0; i < n; i++)
{
printf("%lld\n", ans[i]);
}
return 0;
}
T3:
小H是个善于思考的学生,现在她又在思考一个有关序列的问题。
她的面前浮现出一个长度为n的序列{ai},她想找出一段区间[L, R](1 <= L <= R <= n)。
这个特殊区间满足,存在一个k(L <= k <= R),并且对于任意的i(L <= i <= R),ai都能被ak整除。这样的一个特殊区间 [L, R]价值为R - L。
小H想知道序列中所有特殊区间的最大价值是多少,而有多少个这样的区间呢?这些区间又分别是哪些呢?你能帮助她吧。
【输入格式】
第一行,一个整数n.
第二行,n个整数,代表ai.
【输出格式】
第一行两个整数,num和val,表示价值最大的特殊区间的个数以及最大价值。
第二行num个整数,按升序输出每个价值最大的特殊区间的L.
样例:
Input:
5
4 6 9 3 6
Output:
1 3
2
【数据范围】
30%: 1 <= n <= 30 , 1 <= ai <= 32.
60%: 1 <= n <= 3000 , 1 <= ai <= 1024.
80%: 1 <= n <= 300000 , 1 <= ai <= 1048576.
100%: 1 <= n <= 500000 , 1 <= ai < 2 ^ 31.
思路:
标准思路:
30% :暴力枚举判断。O(n^4)。
60% :特殊区间的特点实际上就是区间最小值等于这个区间的GCD,于是暴力或递推算出每个区间最小值与GCD。而对于最大价值,可以通过二分来进行求解。复杂度O(n ^ 2)。
100%:在60%的基础上,最小值与GCD都使用RMQ算法来求解,对于这道题推荐使用ST表。最大价值仍然使用二分。复杂度O(nlogn)。
当时思路:
当看到数据范围时,我真的方了,说真的,当时就觉得跑一个for已经极限了,跑两个for就该TLE了,GCD更容易TLE(还要判断区间GCD,真不敢写),燃鹅,我还是毅然决然的暴力(想不到更好的方法了),最后写了一个O(n^2)的,真的听天由命了
但是,我居然过了(hahahaha...),真心高兴
然后上代码:
#include<cstdio>
#include<cstring>
#include<iostream>
#define CL(X,N) memset(X, N, sizeof(X))
#define LL long long
using namespace std;
const int maxn=5e5+10;
int n;
int a[maxn];
int l[maxn]={0}, maxlen=0, num=0;
int vis[maxn];
inline void _init() {
freopen("pair.in", "r", stdin);
freopen("pair.out", "w", stdout);
}
int main() {
//_init();
CL(vis, -1);
scanf("%d", &n);
for(int i=1; i<=n; i++) {
scanf("%d", a+i);
}
for(int i=1; i<=n; i++) {
int ll=i, rr=i;
while(ll>1 && !(a[ll-1]%a[i])) ll--;
while(rr<n && !(a[rr+1]%a[i])) rr++;
if(rr-ll>maxlen) {
num=0;
maxlen=rr-ll;
}
if(rr-ll==maxlen && vis[ll]!=maxlen) {
l[num++]=ll;
vis[ll]=maxlen;
}
}
printf("%d %d\n", num, maxlen);
for(int i=0; i<num; i++) printf("%d ", l[i]);
return 0;
}
标程:
#include <algorithm>
#include <iostream>
#include <cstring>
#include <cstdio>
#include <cmath>
using namespace std;
const int N = 5e5 + 3, M = 21;
int n, m, a, ans, l, r, mid, sum, A[N], f[N][M], g[N][M], p[M];
inline int Gcd(const int &x, const int &y) {
return y == 0 ? x : Gcd(y, x % y);
}
bool check(int len) {
int q = log2(len--), k = n + 1 - p[q], j;
for (int i = 1; i <= k; ++i) {
j = i + len;
if (min(f[i][q], f[j - p[q] + 1][q]) == Gcd(g[i][q], g[j - p[q] + 1][q]))
return true;
}
return false;
}
char ch;
inline int read() {
while (ch = getchar(), ch < '0' || ch > '9');
int res = ch - 48;
while (ch = getchar(), ch >= '0' && ch <= '9') res = res * 10 + ch - 48;
return res;
}
char s[10];
inline void print(int x) {
int res = 0;
if (x == 0) putchar('0');
while (x) {
s[++res] = x % 10;
x /= 10;
}
for (int i = res; i; --i) putchar(s[i] + '0');
putchar(' ');
return ;
}
int main() {
freopen("pair.in", "r", stdin);
freopen("pair.out", "w", stdout);
n = read(); m = log2(n);
for (int i = 1; i <= n; ++i) {
a = read();
f[i][0] = g[i][0] = a;
}
for (int i = 0; i <= m; ++i) p[i] = 1 << i;
for (int j = 1; j <= m; ++j) {
int k = n + 1 - p[j];
for (int i = 1; i <= k; ++i) {
f[i][j] = min(f[i][j - 1], f[i + p[j - 1]][j - 1]);
g[i][j] = Gcd(g[i][j - 1], g[i + p[j - 1]][j - 1]);
}
}
l = 1; r = n;
while (l <= r) {
mid = l + r >> 1;
if (check(mid)) l = mid + 1;
else r = mid - 1;
}
ans = r;
if (ans == 1) {
printf("%d %d\n", n, 0);
for (int i = 1; i < n; ++i)
print(i);
printf("%d\n", n);
}
else {
int q = log2(ans--), k = n + 1 - p[q], j;
for (int i = 1; i <= k; ++i) {
j = i + ans;
if (min(f[i][q], f[j - p[q] + 1][q]) == Gcd(g[i][q], g[j - p[q] + 1][q]))
A[++sum] = i;
}
printf("%d %d\n", sum, ans);
for (int i = 1; i < sum; ++i)
print(A[i]);
printf("%d\n", A[sum]);
}
fclose(stdin); fclose(stdout);
return 0;
}
T4:
给定一个{0, 1, 2, 3, … , n - 1}的排列 p。一个{0, 1, 2 , … , n - 2}的排列q被认为是优美的排列,当且仅当q满足下列条件:
对排列s = {0, 1, 2, 3, ..., n - 1}进行n – 1次交换。
- 交换s[q0],s[q0 + 1]
- 交换s[q1],s[q1 + 1]
最后能使得排列s = p.
问有多少个优美的排列,答案对10^9+7取模。
【输入格式】
第一行一个正整数n.
第二行n个整数代表排列p.
【输出格式】
仅一行表示答案。
【样例输入】
3
1 2 0
【样例输出】
1
【样例解释】
q = {0,1} {0,1,2} ->{1,0,2} -> {1, 2, 0}
q = {1,0} {0,1,2} ->{0,2,1} -> {2, 0, 1}
【数据范围】
30%: n <= 10
100%: n <= 50
思路:
30%:
枚举所有排列,判定即可。
100%:
考虑倒着处理,比如交换 (i, i + 1),那么前面的所有数不管怎么交换都无法到后面去(下标恒小于等于i),后面的数也是一样到不了前面。说明这最后一次交换前,就要求对于所有的 x <= i, y > i,px<py。所以交换前左边的数是连续的,右边也是连续的。由于交换前,前面和后面的数是互相不干涉的,所以就归结成了两个子问题。于是我们可以用记忆化搜索来解决这个问题。
设dp[n][low] 代表长度为n,H是{low, low + 1,…,low + n - 1}的排列,且H是p的子序列,在H上优美序列的个数。
我们枚举交换哪两个相邻元素(k,k+1),然后判断k前面的所有数是否都小于后面的所有数,如果是则进行转移dp[n][low] += dp[k][low] * dp[n – k][low + k ] * C(n – 2, n – 1 - k)。
即前面的k个元素与后面的n - k个元素是两个独立的子问题,前面是{low ... low + k - 1}的排列,后面是{low + k ... low + n - 1}的排列,C(n - 2, n - 1 - k)代表的是在交换(k, k + 1)前左右两边还分别要进行n - 2次交换,而每次交换左边与交换右边是不同方案,这相当于n - 2个位置选择n - 1 - k个位置填入,故还需要乘上C(n - 2, n - 1 - k)。时间复杂度为O(n^4)。
标程:
#include <algorithm>
#include <iostream>
#include <cstring>
#include <cstdio>
#include <cmath>
using namespace std;
typedef long long ll;
const int N = 52, Mod = 1e9 + 7;
int n, p[N], dp[N][N], C[N][N];
int Dfs(int len, int low) {
if (dp[len][low] != -1) return dp[len][low];
if (len == 1) return dp[len][low] = 1;
int &res = dp[len][low]; res = 0;
int t[N], m = 0, j, k;
for (int i = 1; i <= n; ++i)
if (p[i] >= low && p[i] < low + len)
t[++m] = p[i];
for (int i = 1; i < m; ++i) {
swap(t[i], t[i + 1]);
for (j = 1; j <= i; ++j)
if (t[j] >= low + i) break;
for (k = i + 1; k <= m; ++k)
if (t[k] < low + i) break;
if (j > i && k > m) {
ll tmp = (ll)Dfs(i, low) * Dfs(m - i, low + i) % Mod;
tmp = tmp * C[m - 2][i - 1] % Mod;
res = (res + tmp) % Mod;
}
swap(t[i], t[i + 1]);
}
return res;
}
int main() {
freopen("swap.in", "r", stdin);
freopen("swap.out", "w", stdout);
scanf("%d", &n);
for (int i = 1; i <= n; ++i) scanf("%d", &p[i]);
memset(dp, -1, sizeof(dp));
for (int i = 0; i <= n; ++i) {
C[i][0] = 1;
for (int j = 1; j <= i; ++j)
C[i][j] = (C[i - 1][j - 1] + C[i - 1][j]) % Mod;
}
Dfs(n, 0);
if (dp[n][0] != -1) cout << dp[n][0] << endl;
else puts("0");
fclose(stdin); fclose(stdout);
return 0;
}
只有标程,因为没做出来...