2018.8.9模拟祭

今天肖大佬对我们还是非常仁慈的,数据不是很强,导致我成功用暴力卡过T1的80pts和T2的100pts……

废话少说,直接看吧。

T1.flower【题目描述】

终于,在一段繁忙的训练之后,到了 NOIP 的举办的时候。同学们坐上了大巴车,享受着沿途的风光,讨论着未解决的问题,憧憬着 NOIP 赛场上切题的样子。很快,大巴车到了大连大学科技楼,也就是辽宁 NOIP 的举办地点。大连大学科技楼是一幢宏伟的建筑,楼前摆放有一排花,共有 n 盆。花一共只有 26 种,分别用 26 个小写英文字母表示,也就是说,楼前的这排花可以用一个仅包含小写英文字母的字符串表示。大连大学雇了一个园艺工人,专门打理科技楼前的花。园艺工人看见你,热情地向你打招呼:“NOIP 加油!”其实,他是有问题想请你帮忙呢!现在园艺工人想再购买一盆花(可以任选 26 种花中的一种),插入到原来的花中间(可以放在整排花的最左侧与最右侧),他想知道在插入一盆花后,能否使整排花左右对称。例如,ababa 是左右对的,

而 abcd 不是。注意:即使原来的一排花已经是左右对称的,也必须再插入一盆花。
【输入格式】
从文件 flower.in 中读入数据。
本题目有多组数据,输入第一行为一个正整数 t,表示数据组数。
接下来 t 行,每行包含一个正整数 n 和一个长度为 n 的字符串,分别表示花的数
量与花构成的序列。
【输出格式】
输出到文件 flower.out 中。
对于每组数据输出一行。若再插入一盆花之后能使整排花左右对称,输出 Yes,否
则输出 No(注意大小写)。
【样例 1 输入】
4
4 abcd
4 aabb
4 aaaa
10 abcdefecba
【样例 1 输出】
No
No

Yes

Yes

这题怎么做?虽然说很简单,不过我的字符串很弱,并没有成功的想到正解。所以直接使用了暴力模拟,枚举每种字母,插在所有的位置里,复杂度n^2*26,期望得分50,但是由于随机出来的数据比较分散导致能卡过80pts。

正解很简单,我们从这个字符串的两边向中间找,找到第一个相对应字符不一样的位置。记录下来这个位置,左边为l,右边为r。之后只要判断一下l ~ r-1和l+1 ~ r这两段,只要其中有一个是回文串就行。

以样例第四行为例,不一样的位置是4,7,之后发现5~7是回文串说明可行。原因很简单,如果一个串是回文串,那么它一定在插入一个新的之后能形成回文串。然后,在你找到失配的位置之前,他们都是可以回文匹配的,所以,如果判断的两个区间中任意一个是回文串,另外一个多出来的位置就可以被插入字符补充,但如果两个都不是的话无论如何也组不出来了。

上一下代码。

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
#include<cmath>
#include<set>
#define rep(i,a,n) for(int i = a;i <= n;i++)
#define per(i,n,a) for(int i = n;i >= a;i--)
#define enter putchar('\n')
using namespace std;
typedef long long ll;
const ll INF = 1e9;
const int M = 1000005;

int read()
{
    int ans = 0,op = 1;
    char ch = getchar();
    while(ch < '0' || ch > '9')
    {
        if(ch == '-') op = -1;
        ch = getchar();
    }
    while(ch >= '0' && ch <= '9')
    {
        ans *= 10;
        ans += ch - '0';
        ch = getchar();
    }
    return ans * op;
}
int t,n,l,r;
char s[M];
bool flag;
bool check()
{
    bool pd1,pd2;
    pd1 = pd2 = 1; 
    int k1 = l + 1,k2 = r;
    while(k1 < k2)
    {
        if(s[k1] != s[k2]) 
        {
            pd1 = 0;
            break;
        }
        k1++,k2--;
    }
    k1 = l,k2 = r - 1;
    while(k1 < k2)
    {
        if(s[k1] != s[k2])
        {
            pd2 = 0;
            break;
        }
        k1++,k2--;
    }
    return pd1 | pd2;
}
int main()
{
//    freopen("flower.in","r",stdin);
//    freopen("flower.out","w",stdout);
    t = read();
    while(t--)
    {
        n = read(),l = 1,r = n;flag = 0;
        scanf("%s",s+1);
        rep(i,1,n/2) 
        {
            l = i,r = n - i + 1;
            if(s[i] == s[n-i+1]) continue;    
            else 
            {
                if(check()) printf("Yes\n");
                else printf("No\n");
                flag = 1;
                break;
            }
        }
        if(!flag) printf("Yes\n");
    }
    return 0;
}

T2.road

【题目描述】在大连大学园艺工人的祝福下,你顺利地结束了 NOIP 第一天的比赛。走出考场,漫步于大连大学美丽而静谧的校园中,你感到心情无比舒畅。正当你向大连大学宾馆走去的时候,一个陌生人叫住了你。原来他是大连大学校园规划部主席,他早知道你是一个无所不能的 OIer,并且尤其擅长道路规划。他向你请教了一个问题,希望你能给出答案。大连大学可以看做一个由 n 个结点,m 条有向道路构成的有向图。结点编号为 1到 n,每条道路有一个长度(为正整数)。其中,科技楼在 s 号结点,大连大学宾馆在 t号结点。主席想知道,校园中的每条道路是否一定出现在从 s 到 t 的最短路径上,如果不一定,那么至少要将这条道路的长度减少多少,才能使这条道路一定出现在从 s 到 t的最短路径上(减少后的长度必须为一个正整数)。

【输入格式】
从文件 road.in 中读入数据。
输入第一行包含 4 个正整数 n,m, s,t(n ≤ 2 × 10 5 ;m ≤ 4 × 10 5 ;1 ≤ s,t ≤ n),含义如题。
接下来 m 行,每行 3 个正整数 u,v,w(1 ≤ u,v ≤ n;w > 0),分别表示一条边的起点、终点,长度。
保证从 s 能到达 t。
【输出格式】
输出到文件 road.out 中。
输出 m 行。每行代表一条边的答案:
• 若该边一定出现在从 s 到 t 的最短路上,输出 YES;
• 若该边不一定出现在从 s 到 t 的最短路上,但能通过改变该边长度为一正整数,使得该边一定出现在从 s 至 t 的最短路上,输出 CAN 和一个正整数,用一个空格隔开,其中的正整数表示该边长度减少的最小值;
• 否则,输出 NO。
【样例 1 输入】
6 7 1 6
1 2 2
1 3 10
2 3 7
2 4 8
3 5 3
4 5 2
5 6 1
【样例 1 输出】
YES
CAN 2
CAN 1
CAN 1
CAN 1
CAN 1
YES

这题当时觉得其实不是很难,只要建一个正图一个反图,跑两遍dijk的堆优化预处理出来s,t点到各个点的距离,之后枚举每条边判断就可以。不过后来发现题中可能存在多条最短路,这时候我使用了一种非常诡异的暴力(不过后来被CF上的数据卡掉了)

就是对于每条边,设s到其距离为x,t到其距离为y那么只有在x+y+e[i].v == cur(最短路长度)的时候,其有可能在一条最短路上。(也就是在最短路图上)所以我找到它在正图上的出发点能引出来的所有边,再找到它在的到达点在反图上引出的所有边进行判断。把其中所有的x+y+e[i].v == cur的情况都更新。这种做法能过本题的所有数据点……不过它的正确性有待商榷。

正解有那么几种,一种是像SZQ巨佬一样计算一下最短路计数。然后对于每条在最短路图上的边,如果其最短路计数等于图中的最短路计数,那么他必然在最短路上。

(然后连这个算法都会被CF上某丧心病狂的Hacker用数据hack掉,原因是他把所有边权全部设为1e6导致最短路计数极大)

于是我们考虑对最短路计数取模,不过如果你用1e9+7还是会被hack掉,原因不明……

反正换几个质数就过了,可以使用非法质数。然后不用质数也行,反正很奇怪……

然后标程给的是Tarjan求最短图的桥。这个做法好像更有道理一点,但是标程写的奇丑无比,还是把SZQ巨佬的代码copy上来吧

#include<cstdio>
#include<cctype>
#include<algorithm>
#include<cstring>
#include <vector>
#include <queue>
using namespace std;
#define int ll
#define pp pair<ll,int>
#define ll unsigned long long
#define fi first
#define se second
const int mod = 19260817;
int read()
{
    int x = 0;
    int f = 1;
    char ch = getchar();
    while(!isdigit(ch))
    {
        if(ch == '-')
            f = -1;
        ch = getchar();
    }
    while(isdigit(ch))
        x = (x << 1) + (x << 3) + (ch ^ 48),ch = getchar();
    return x * f;
}
void print(int x)
{
    if(x < 0)
    {
        putchar('-');
        print(-x);
        return;
    }
    char ch = x % 10 + '0';
    if(x < 10)
    {
        putchar(ch);
        return;
    }
    print(x / 10);
    putchar(ch);
}
const int M = 400100,N = 200100;
struct edge
{
    int to,w,next;
}e[2][M];
int head[2][N],cnt[2];
void add(int u,int v,int w,int pos)
{
    e[pos][++cnt[pos]].to = v;
    e[pos][cnt[pos]].w = w;
    e[pos][cnt[pos]].next = head[pos][u];
    head[pos][u] = cnt[pos];
}
void add_edge(int u,int v,int w)
{
    add(u,v,w,0);
    add(v,u,w,1);
}
struct edge1
{
    int u,v,w;
}map[M];
ll dis[2][M];
ll num[2][M];
int s,t,n,m;
void dijkstra(int S,int pos)
{
    priority_queue<pp,vector<pp>,greater<pp> > Q;
    dis[pos][S] = 0;
    num[pos][S] = 1;
    Q.push(pp(0,S));
    while(!Q.empty())
    {
        pp p = Q.top();
        Q.pop();
        int u = p.se;
        if(dis[pos][u] < p.fi) 
            continue;
        for(int i = head[pos][u];i;i = e[pos][i].next)
        {
            int v = e[pos][i].to,w = e[pos][i].w;
            if(dis[pos][v] > dis[pos][u] + w)
            {
                dis[pos][v] = dis[pos][u] + w;
                Q.push(pp(dis[pos][v],v));
                num[pos][v] = num[pos][u];
            }
            else 
                if(dis[pos][v] == dis[pos][u] + w)
                    num[pos][v] = (num[pos][v] + num[pos][u]) % mod;//最短路计数
        }
    }
}
main()
{
    n = read();
    m = read();
    s = read();
    t = read();
    for(int i = 1;i <= n;i++)
        dis[0][i] = dis[1][i] = 100000000000000000;
    memset(head,0,sizeof(head));
    for(int i = 1;i <= m;i++)
    {
        int u,v,w;
        u = read();
        v = read();
        w = read();
        map[i].u = u;
        map[i].v = v;
        map[i].w = w;
        add_edge(u,v,w);
    }
    dijkstra(s,0);
    dijkstra(t,1);
    for(int i = 1;i <= m;i++)
    {
        int f = map[i].u,to = map[i].v,w = map[i].w;
        if(dis[0][f] + dis[1][to] + w == dis[0][t] && (num[0][f] * num[1][to]) % mod == num[0][t] % mod)       
            puts("YES");
        else
        {
            long long a = dis[0][t] - dis[0][f] - dis[1][to];
            if(a - 1 > 0)
                printf("CAN %I64d\n",w - a + 1);
            else
                puts("NO");
        }
    }
    return 0;
}

T3.puzzle

【题目描述】
NOIP 结束了,你踏上了归程。在大巴车上,你很无聊,于是你想了一个谜题来娱乐一下。
若一个二维数组每个元素都是 (或) 中的一个,那么这个数组被成为括号二维数组。从二维数组的左上角 (1,1) 出发,每次向右或向下移动一格,直到走到右下角(n,m),这样的路径被成为单调路径。自然地,一条单调路径上的所有字符按顺序相连可以构成一个括号串。
如果一个括号二位数组的每一条单调路径所对应的括号串都是匹配的括号串,那么这个括号二维数组被称为匹配的括号二维数组。
现在定义两个相同大小的匹配的括号二维数组的一个比较函数(比较数组 a 和 b)。
假设二维数组中每个元素都有一个优先级。优先级为 1 到 nm 的整数,且两两不同(设优先级储存在一个和 a,b 同样大小的二维数组 c 中)。然后找到所有 a i,j , b i,j 的位置。如果有多个这样的位置,就选择 c i,j 最小的位置。如果 a i,j =(,则 a < b,否则 a > b;若没有这样的位置,则 a = b。
有了比较函数,我们可以给一定大小所有匹配的括号二维数组从小到大排序,你希望找出具有指定大小的第 k 个匹配的括号二维数组。
数据保证第 k 个匹配的括号二维数组存在。
【输入格式】
从文件 puzzle.in 中读入数据。
第一行有三个正整数 n,m,k(1 ≤ n,m ≤ 500;1 ≤ k ≤ 10 18 ),分别表示二维数组的行数、列数、你希望找到的匹配的括号二维数组的序数。
接下来 n 行,每行 m 个正整数,表示优先级数组(优先级两两不同)。
【输出格式】
输出到文件 puzzle.out 中。
输出 n 行,每行一个长度为 m 的字符串,表示所求得的二维数组。
【样例 1 输入】
3 2 2
3 6
1 4
2 5
【样例 1 输出】
()
)(
()

这题自己做的时候一脸懵逼,也没看懂题,也不知道题要干嘛,不过听完讲解还是明白了很多。

首先,能匹配上的括号二维数组有一个性质,那就是每条从右上到左下的对角线上的字符一定相同。这个是必然的,否则这个括号二维数组就没法匹配。所以我们只要确定了任意一条有序路径,就能确定整个矩阵。

那我们直接确定第一行和最后一列构成的路径好了!因为每个格子都有一个优先级,而且在优先级相同的情况下,‘(’是更小的那一个,所以我们从优先级最小的那个格子所在的对角线开始。先把其设成左括号,之后,如果在这种情况下所能生成的匹配括号矩阵个数要比k多,那么这个位置就确定为左括号,之后继续从优先级次小那个格子所在对角线找……如果比k个少那么就确定为右括号。(仿佛在线段树上二分)

然后确定生成的匹配括号矩阵个数当然是DP。用dp[i][j]表示枚举到第i位,有j个未匹配的左括号时,有多少种匹配情况。如果这位没有限制,他可以向dp[i+1][j+1],dp[i+1][j-1]上转移。然后有哪种不行就不转移。

这样就求完了。

有一份原版CF题解,写的不错,虽然是纯英文……

Let's reduce the problem to a one-dimensional matrix. Consider a monotonous path (1, 1), (1, 2), ..., (1, m - 1), (1, m), (2, m), ..., (n - 1, m), (n, m) which has correct bracket sequence. Now, in this way a cell (1, m) can be replaced on (2, m - 1) and still be a monotonous way and will form the correct sequence of the bracket. So in the cells of (1, m) and (2, m - 1) is one type of bracket. Proceeding further (eg to replace (1, m - 1) on (2, m - 2) or (2, m) on (3, m - 1)) can be seen that in cells (i, j) and (i - 1, j + 1) is one type of bracket. Then we get not two-dimensional array n × m, a one-dimensional size n + m - 1. For each position can be determined what her highest priority, ie for cell i (1 ≤ i ≤ n + m - 1), the priority will be equal to the minimum value of px, y where 1 ≤ x ≤ n1 ≤ y ≤ m and x + y - 1 = i.
Let's iterate through the positions, starting with the highest priority. Let's put in this position the bracket "(" and consider how many ways can complete the remaining brackets to get the correct bracket sequence. If the number of ways of not less than k, then leave in this position "(", or reduce the k on the number of ways and put in this positions bracket ")". And so let's iterate through all items. In order to calculate the number of ways each time dynamics is calculated on two parameters fi, j, where i is the number of processed positions, and j is the number of opened brackets. If the position of i + 1 bracket is not defined yet then you can go to fi + 1, j + 1 or fi + 1, j - 1, if defined then only fi + 1, j + 1 or only fi + 1, j - 1, depending on opening or closing bracket respectively.

上代码。

#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<iostream>
#define rep(i,a,n) for(int i = a;i <= n;i++)
#define per(i,n,a) for(int i = n;i >= a;i--)
#define enter putchar('\n')

using namespace std;
typedef long long ll;
const int M = 1005;

ll read()
{
    ll ans = 0,op = 1;
    char ch = getchar();
    while(ch < '0' || ch > '9')
    {
        if(ch == '-') op = -1;
        ch = getchar();
    }
    while(ch >= '0' && ch <= '9')
    {
        ans *= 10;
        ans += ch - '0';
        ch = getchar();
    }
    return ans * op;
}

ll n,m,k,l,a[M],d[M],x;
ll dp[M][M];
char s[M];
bool qcmp(const ll &p,const ll &q)
{
    return a[p] < a[q];
}
int main()
{
    n = read(),m = read(),k = read();
    l = n + m - 1;
    rep(i,0,l - 1) a[i] = m * n,d[i] = i;
    rep(i,0,n - 1)
        rep(j,0,m - 1) a[i+j] = min(a[i+j],read());
    sort(d,d+l,qcmp);//超级nb的排序
    rep(g,0,l-1)
    {
        s[d[g]] = '(',dp[0][0] = 1;
        rep(i,0,l-1)
        {
            for(int j = i & 1;j <= i && j <= l - i;j += 2)    
            {
                if(dp[i][j])
                {
                    if(dp[i][j] > k) dp[i][j] = k;
                    if(s[i] != ')') dp[i+1][j+1] += dp[i][j];
                    if(s[i] != '(' && j) dp[i+1][j-1] += dp[i][j];
                    dp[i][j] = 0;
                }
            }
        }
        if(dp[l][0] < k)
        {
            k -= dp[l][0];
            s[d[g]] = ')';
        }
        dp[l][0] = 0;
    }
    rep(i,0,n-1)
    {
        rep(j,0,m-1) printf("%c",s[i+j]);
        enter;
    }
    return 0;
}
/*
3 2 2
3 6
1 4
2 5
*/

不知不觉到了8.10……希望今明两天模拟正常……

posted @ 2018-08-10 00:02  CaptainLi  阅读(144)  评论(0编辑  收藏  举报