[ICPC训练联盟 周赛2]UCF Local Contest 2015

UCF Local Contest 2015

虽然下午有一节计组…但是还是打了…毕竟自己太菜了要多训练…

正好后面4题都感觉不太写得出来…2个半小时就去上课了。

参考博客:
[ICPC训练联盟周赛2][东华大学] UCF Local Contest 2015 题解 -viaAE酱
好像就是东华大学题解?


A-D
花式签到。

E
简单的几何运算。
想到前几天的CTU的自适应辛普森还是那个题自闭。

F
规律显而易见,剩下的就是构造交替01串。

G
假hanoi塔。容易注意到最大元素一定要有一条路走到(n, n), 所以只有 ( n − 1 ) 2 (n - 1)^2 (n1)2个位置可以用来中转小塔,那么只要所有元素超过 ( n − 1 ) 2 + 1 (n - 1)^2+1 (n1)2+1就不可能成功转移位置了。


补题
H
后四题中唯一一个尝试做的题,但是假了…中午又有点困&&下午还有课就溜了。

当意识到贪心是不行的之后,注意到题目的范围不大,暗示正确做法应该是搜索。

最朴素的想法就是 2 100 2^{100} 2100枚举,中间可能可以进行一些最优化、可行性剪枝,但是感觉复杂度比较玄学,就放弃了。

不同于以往朝四个方向搜索,这道题由于染色的特殊性,我们可以指定一个方向依次进行贴邮票,不妨选取为一行一行从左往右地尝试贴邮票。对于某个位置而言,如果想要以该点为中心贴一张邮票,那么其十字范围中不能出现空白部分,并且其十字范围的黑点应该至少一个没有被覆盖过,只有满足了这样的条件这个点才可以贴一次邮票,不然的话跳过该点搜索下一个点。

总体的搜索策略如上。但是如果不进行精巧的剪枝还是无限TLE:
一个最容易想到的是,如果当前贴的邮票个数加上至少还要贴的邮票个数大于当前答案,那么该答案显然不是最优。但如果仅仅把至少还要贴的邮票个数left / 5表示,那么会收获TLE;改为玄学的(left - 1) / 5 + 1就可以在规定时间内跑出答案。大概原因在于这个矩阵只有100的大小,顶函数和底函数可能的差值(5)相对而说已经较大了,我们应该尽量地使该失败条件覆盖更多的情况。
UPD: 经过测试,标程程序哪怕这里剪枝弱一点也能过,我的程序常数巨大就被卡死了…找了半天也不知道是为什么…可能是为了简洁用了dx dy数组以及调用好多次函数的原因?QwQ
另一个是可行性剪枝。在该矩形四个边界上的黑点是一定要靠邻点才能进行染色的,所以如果递归搜索的时候出现了这种情况,那么就只需要考虑该方格一定要染色的情况。

代码

const int maxn = 12;
const int inf = 2147483647;

int n, m;
char s[maxn][maxn];
int ans = inf;
int temp[5];

int dx[5] = {-1, 0, 0, 0, 1};
int dy[5] = {0, -1, 0, 1, 0};

inline void color(int x, int y, char c){
    for(int k = 0; k < 5; k++) s[x + dx[k]][y + dy[k]] = c;
}

inline void back(int x, int y, int temp[5]){
    for(itn k = 0; k < 5; k++) s[x + dx[k]][y + dy[k]] = temp[k];
}

inline void save(int x, int y, int temp[5]){
    for(itn k = 0; k < 5; k++) temp[k] = s[x + dx[k]][y + dy[k]];
}

void dfs(int x, int y, int times, int left){
    //成功终止条件
    if(left == 0){ ans = min(ans, times); return;}
    //向右下角移动到出界。
    if(x >= n || y >= m) return;
    
    //最优化剪枝。
    if(times + (left - 1)/ 5 + 1 >= ans) return;
    
    int cnt = 0, fail = 0;
//    判断当前点是否需要染色。
    for(int i = 0; i < 5; i++){
        if(s[x + dx[i]][y + dy[i]] == '.') fail = 1;
        if(s[x + dx[i]][y + dy[i]] == '#') cnt++;
    }
    if(!fail && cnt){
        int temp[5]; save(x, y, temp); color(x, y, '@');
        if(y + 1 < m) dfs(x, y + 1, times + 1, left - cnt);
        else dfs(x + 1, 2, times + 1, left - cnt);
        back(x, y, temp);
    }
    if(x == 2 && s[1][y] == '#') return;
    if(y == 2 && s[x][1] == '#') return;
    if(x == n - 1 && s[n][y] == '#') return;
    if(y == m - 1 && s[x][m] == '#') return;
    if(y + 1 < m) dfs(x, y + 1, times, left);
    else dfs(x + 1, 2, times, left);
}

int main(){
//    Fast;
    int t; scanf("%d", &t); for(int _ = 1; _ <= t; _++){
        ans = inf;
        printf("Image #%d: ", _);
        scanf("%d %d", &n, &m);
        for(int i = 1; i <= n; i++) scanf("%s", s[i] + 1);
        if(s[1][1] == '#' || s[1][m] == '#' || s[n][1] == '#' || s[n][m] == '#') puts("impossible");
        else{
            int left = 0;
            for(int i = 1; i <= n; i++) for(int j = 1; j <= m; j++) if(s[i][j] == '#') left++;
            dfs(2, 2, 0, left);
            if(ans == inf) puts("impossible");
            else printf("%d\n", ans);
        }
        printf("\n");
    }
    return 0;
}

I
一个新知识点: 竞赛图中找曼哈顿路径

参考博客:
竞赛图详解
任意竞赛图都有哈密顿路径 (证明)

如果当前已经获得了一段路径,考虑如何将新进入的一个点插入该路径。

  • 若当前点有一条指向路径开头的边,那么直接令其为开头即可;
  • 若当前点可以成为以结尾为起点的一条边的另一个端点,将其加入到结尾即可;
  • 不然,该路径中一定有一个点可以满足有: p - x - p->next, p是路径中某个节点。这是因为已经有了head - x - tail,所以中间的路径节点与x之间一定会出现指向方向的变化,取第一次出现的位置并在此插入x即可。

用单向链表维护这个结构即可。

算法时间复杂度 O ( n 2 ) O(n^2) O(n2).

代码

const int maxn = 500 + 10;

struct Link{
    int key;
    struct Link *next = nullptr;
};

int edge[maxn][maxn];

int main(){
//    Fast;
    int t; scanf("%d", &t); for(int _ = 1; _ <= t; _++){
        int n; scanf("%d", &n);
        for(int i = 1; i <= n; i++) for(int j = 1; j <= n; j++) scanf("%d", edge[i] + j);
        
        Link *start = (Link*)malloc(sizeof(Link)); start->key = 1; start->next = nullptr;
        for(int i = 2; i <= n; i++){
            Link *temp = (Link*)malloc(sizeof(Link)); temp->key = i; temp->next = nullptr;
            if(edge[i][start->key]){
                temp->next = start; start = temp;
                continue;
            }
            Link *p = start;
            while(p->next != nullptr){
                if(edge[p->key][i] && edge[i][p->next->key]){
                    temp->next = p->next; p->next = temp;
                    break;
                }
                p = p->next;
            }
            p->next = temp;
        }
        Link *p = start;
        while(p != nullptr){
            printf("%d ", p->key); p = p->next;
        }
        printf("\n");
    }
    return 0;
}

J
网络流相关。
等之后找一周系统的刷一遍网络流再回做这道题。

K
分形图递归算法。

因为在模4下进行乘法,所以我们只需要考虑 x ≡ 0 , 1 , 2 , 3 ( m o d 4 ) x ≡0,1,2,3(mod4) x0,1,2,3(mod4)的情况即可。

x ≡ 0 ( m o d 4 ) x≡0(mod4) x0(mod4)时,显然无解。
x ≡ 2 ( m o d 4 ) x≡2(mod4) x2(mod4)时,仅需考虑第0项和第1项的乘积,即 n x nx nx模4的值,显然当且仅当n为奇数时其乘积模4余2,此时取1、2输出答案3。

x ≡ 1 , 3 ( m o d 4 ) x≡1,3(mod4) x1,3(mod4)时,所有的 x i x^i xi均为奇数,奇数乘奇数仍然是奇数;另一方面,一个奇数当且仅当乘上一个偶数后才能满足模4余2。所以我们选取所有的奇数 C n i C_n^i Cni,取一个最大的偶数 C n i C_n^i Cni,将其全部相乘即可获得下标之和最大、乘积模4余2的一串乘法。

下面考虑如何快速的在2^31的范围内快速找到上述要求的两个量:

先考虑如何求所有满足 ( i n ) (_i^n) (in)为奇数的 i i i的和。打出表后发现该分形图构造规律:对一个三角形在其下方复制一次,在其右下方复制一次,形成一个新的大三角形;对该大三角形递归的进行复制操作。
那么要求某一层的 1 1 1的个数就只需要递归地在其母三角形中找 1 1 1然后将个数乘 2 2 2即可。

然后考虑如何找某一行最右边的2的位置。这个分形图比奇数查找的分形图要复杂,三角形在向下方、右下角复制后,中心三角形也会复制两次成为大三角形的中心三角形。所以我们先尝试在母三角形中找2,如果找到了显然该位置加上一定的偏移量就是大三角中最远的一个 2 2 2;如果没有找到也不意味着大三角形中该行没有 2 2 2, 这个 2 2 2还有可能出现在中央大三角中,所以我们还需要对此进行一次讨论、求解。

粗糙(看到眼瞎)的表

代码

ll x, n;

//杨辉三角从1开始编行
ll count1(ll n){
    ll line = 1;
    while(line <= n) line <<= 1; line >>= 1;
    if(line == n) return n;
    return count1(n - line) << 1;
}

ll counti(ll n){
    ll line = 1;
    while(line <= n) line <<= 1; line >>= 1;
    if(n == line) return n * (n + 1) / 2;
    return (counti(n - line) << 1) + (count1(n - line) * line);
}

ll get2(ll n){
    ll line = 1;
    while(line <= n) line <<= 1; line >>= 1;
    if(n == line) return -1;
    ll temp = get2(n - line);
    if(temp == -1){
        if(n - line <= line >> 1) return (line >> 1) + n - line;
        return -1;
    }
    return temp + line;
}

int main(){
//    Fast;
    int t; scanf("%d", &t); for(int _ = 1; _ <= t; _++){
        scanf("%lld %lld", &x, &n);
        if(x % 4 == 0) printf("0");
        else if(x % 4 == 2){
            if(n % 2 == 0) printf("0");
            else printf("3");
        }
        else{
            ll temp = get2(n + 1);
            if(temp == -1) printf("0");
            else printf("%lld", get2(n + 1) + counti(n + 1));
        }

        printf("\n");
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值