解题报告(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次交换。

  1. 交换s[q0],s[q0 + 1]
  2. 交换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;
}

只有标程,因为没做出来...

posted @ 2018-06-02 22:39  Nomaldisk  阅读(163)  评论(0编辑  收藏  举报