虽然下午有一节计组…但是还是打了…毕竟自己太菜了要多训练…
正好后面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
(n−1)2个位置可以用来中转小塔,那么只要所有元素超过
(
n
−
1
)
2
+
1
(n - 1)^2+1
(n−1)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) x≡0,1,2,3(mod4)的情况即可。
当
x
≡
0
(
m
o
d
4
)
x≡0(mod4)
x≡0(mod4)时,显然无解。
当
x
≡
2
(
m
o
d
4
)
x≡2(mod4)
x≡2(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)
x≡1,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;
}