Codecraft-20 (Div.2) (B-E)

Codecraft-20 (Div.2)


B
容易发现如果总共翻转次数 n − k + 1 n - k + 1 nk+1为奇数的话,最终串的后缀就会是 s k − 1 s k − 2 … s 1 s_{k-1}s_{k-2}\dots s_1 sk1sk2s1为偶数的话就是正序的 s 1 , … , s k − 1 s_1,\dots,s_{k-1} s1,,sk1;而前缀都是顺序的 s k , … , s n s_k,\dots,s_n sk,,sn。所以只需要枚举所有的k, O ( n 2 ) O(n^2) O(n2)的搜索即可。

最终是FST13
看了半天感觉算法是没有问题的…就去看数据…好像第一组输出和第二组输出有点相似…感觉是C风格字符串没有终止\0的感觉…查了一下果不其然…自己在构造temp串的时候没有在末尾加上终止符号\0…所以在多组询问的情况下就会被之前未清空的信息干扰。。大坑

C
挣扎了大概10min…没有经得住诱惑,写了一发NTT…TLE…

当时我发现我的板子好像一点都不好用…(最终去了luogu找了自己AC的代码来用)为什么我只在自己的学习博客中写了关键代码…真要做的时候谁还高兴去仔细推导其余细节部分…我要在之后某个合适的时机完善一下自己的板子系统…tcl

之后好像发现了这道题其实是个假题…最开始忽视了 a i a_i ai为1的情况下,各个因子的个数就不会受到限制的情况。这样考虑的话每个素因子只可能分配给某一项系数,所以根据 a 0 a_0 a0进行一番分类讨论就容易获得一个解。后来忽然意识到如果存在一个 a i a_i ai为1的情况,就不需要满足每个因子只被分配一次的条件,那么也就不能根据 a 0 a_0 a0获得一个可行解…在推导这种特殊情况时我发现找到两个多项式中各自第一个在模 p p p下不为零的因子就可以获得一个可行解…??? 证明容易: 这两个小系数贡献的大系数中除了这一对其余的积均为0…

10行AC…想了一年…


补题

D
容易想到对于那些死循环的点,要么移动到另一组已经构造好的死循环中,要么直接与另一个相邻的死循环点构成一个2元死循环;而对于那些有终点的点,只需要从这个终点出发,bfs不断遍历四个邻点,将那些能遍历到的、目的地为该终点的点的方向设置为对应方向即可。

这样子处理完每一个点之后,如果仍有点没有置上方向,说明这是一个孤立点,此时就应该输出INVALID;如果某个点成为了其他点的终点,那么这个点也应该成为自己的终点,不然则不符合题意,也应该输出INVALID(这一块最开始没有想到,WA了一发才改出来)。

代码

const int maxn = 1e3 + 10;
const int p = 1e3 + 5;
int n;
int a[maxn][maxn];
set<int> end;

int dx[4] = {0 ,0 , 1, -1};
int dy[4] = {1, -1, 0, 0};
char key[4] = {'L', 'R', 'U', 'D'};
char ori[4] = {'R', 'L', 'D', 'U'};

int vis[maxn][maxn];
char ans[maxn][maxn];

int in(int x, int y, int i){
    int nx = x + dx[i], ny = y + dy[i];
    if(nx >= 0 && ny >= 0 && nx < n && ny < n) return 1;
    return 0;
}


queue<int> q;
int bfs(int x, int y){
    if(a[x][y] == -1){
        vis[x][y] = 1;
        for(int i = 0; i < 4; i++) if(in(x, y, i)){
            int nx = x + dx[i], ny = y + dy[i];
            if(a[nx][ny] != -1) continue;
            
            if(vis[nx][ny]) ans[x][y] = ori[i];
            else{
                vis[nx][ny] = 1;
                ans[nx][ny] = key[i]; ans[x][y] = ori[i];
            }
            return 1;
        }
    }
    else{
        int des = a[x][y]; if(a[des / p][des % p] != des) return 0;
        q.push(a[x][y]);  vis[des / p][des % p] = 1; ans[des / p][des % p] = 'X';
        while(!q.empty()){
            int temp = q.front(); q.pop();
            x = temp / p; y = temp % p;
            for(int i = 0; i < 4; i++){
                if(in(x, y, i) && a[x + dx[i]][y + dy[i]] == des && !vis[x + dx[i]][y + dy[i]]){
                    vis[x + dx[i]][y + dy[i]] = 1;
                    ans[x + dx[i]][y + dy[i]] = key[i];
                    q.push((x + dx[i]) * p + y + dy[i]);
                }
            }
        }
        
    }
    return 1;
}

int main(){
//    Fast;
    scanf("%d", &n);
    int x, y;
    for(int i = 0; i < n; i++) for(int j = 0; j < n; j++){
        scanf("%d %d", &x, &y); x--; y--;
        if(x == -2) a[i][j] = -1;
        else a[i][j] = x * p + y;
    }
    int fail = 0;
    for(int i = 0; i < n; i++) for(int j = 0; j < n; j++) if(!vis[i][j]){
        fail |= bfs(i, j) == 0;
    }
    
    for(int i = 0; i < n; i++) for(int j = 0; j < n; j++) fail |= ans[i][j] == 0;
    if(fail) puts("INVALID");
    else{
        puts("VALID");
        for(int i = 0; i < n; i++) printf("%s\n", ans[i]);
    }
    return 0;
}

自己的代码有点丑…可以参考tutorial的代码办法,不使用dx、dy而直接手动分类,将置值的字符传入递归函数从而进行赋值操作,可以省去二位哈希和方向的设置,会漂亮很多。

E
我姑且将其称为一道 DP 题…因为我实在不是很理解bitmask到底是个啥…莫非就是将状态用二进制数表示吗…打算之后做一个bitmask的专题,正好problemset里有这个标签。

并不能想到正确的dp办法(之后也打算做一个dp的专题),学习于题解:

首先将所有人按照 a i a_i ai降序排序,然后设置dp[i][sts],i表示目前已经考虑过1-i人员的所属问题,sts用二进制数表示所有职位被占据的情况,dp数组记录当前状态下可能获得的最大价值。对于当前考虑的第i个人而言,他可以去1-p的所有职务,也可以被选择为观众,也可以啥都不干。如果第i个人将要参与职务,根据sts容易获得递推关系;不然的话,第i个人就有可能去观众席: 因为我们事先将 a i a_i ai降序排序,所以如果第i人不参与职务,并且观众席仍有空位的话,选择他去观众席一定更优,那么就可以据此更新dp数组值;如果观众席也满了,当前状态所可能拥有的最大值就只能继承前一人的最大值了。

这里有一个地方要注意,如果当前状态sts被占据的职位个数已经大于i - 1了,那么此时的状态显然已经不合理,应该直接继承前一人的情况。也正因为第一个人也可能要继承前一人,所以我们将数组从1开始编号,将0数组的值自然置为0。

代码

const int maxn = 1e5 + 10;
int n, p, k;

struct node{
    int ai, p[8];
}a[maxn];

int getcnt(int sts){
    int res = 0;
    for(int i = 0; i < p; i++) if(sts & (1 << i)) res++;
    return res;
}

bool cmp(node a, node b){
    return a.ai > b.ai;
}

ll dp[maxn][(1 << 7) + 10];

int main(){
//    Fast;
    scanf("%d %d %d", &n, &p, &k);
    for(int i = 1; i <= n; i++) scanf("%d", &a[i].ai);
    fro(int i = 1; i <= n; i++) for(int j = 0; j < p; j++)
        scanf("%d", a[i].p + j);
    sort(a + 1, a + n + 1, cmp);
    for(int i = 1; i <= n; i++){
        for(int sts = 0; sts < 1 << p; sts++){
            //第i个人担任第j个职务。
            for(int j = 0; j < p; j++){
                //第j个职务已经有人担任了。
                if(sts & (1 << j)) continue;
                
                dp[i][sts | 1 << j] = max(dp[i][sts | 1 << j], dp[i - 1][sts] + a[i].p[j]);
            }
            //第i个人不担任职务,如果仍有空缺就去观众。
            int cnt = getcnt(sts);
            if(i - 1 - cnt < k && i - 1 - cnt >= 0)
            	dp[i][sts] = max(dp[i][sts], dp[i - 1][sts] + a[i].ai);
            else dp[i][sts] = max(dp[i][sts], dp[i - 1][sts]);
        }
    }
    printf("%lld\n", dp[n][(1 << p) - 1]);
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值