2019 icpc 南京区域赛
前言:感觉5年前拿牌应该比现在简单很多()
Contest Link
A - A Hard Problem
给定\(n\),求最小的\(k\)使得集合\(\{1,2,...,n\}\)中大小为\(k\)的任何子集中,都存在一个数为另一个数的约数。
很显然,对于大小相等的子集,数字越大越不容易满足性质。易得答案为\(\lceil(n/2)\rceil+1\)。
int T = next();
while(T--) cout<<(next()+1)/2+1<<endl;
C - Digital Path
\(n*m\)的网格,每个格上有数。求满足以下条件的路径个数:
- 相邻格共享一条边
- 从起点到终点,所经过格上的数依次递增1
- 至少经过4格
- 路径具有最大性,不能被延长后仍然满足其它条件
有人忘了%MOD吃了一发罚时还多检查了5分钟
很显然,这是一个非常清楚的dp,或者说模拟。只需要从小到大遍历各个数,尝试走到邻居就可以了。
因为路径具有最大性,注意初始化的时候只把不比邻居大1的位置设为1,统计答案时只计不比邻居小1的位置。
const int dx[] = {1, -1, 0, 0}, dy[] = {0, 0, 1, -1};
int dp[U][U][5], w[U][U];
int n, m;
set<int> uni;
cin>>n>>m;
hrp(i, 1, n) hrp(j, 1, m) cin>>w[i][j], uni.insert(w[i][j]), pos[w[i][j]].push_back({i, j});
hrp(i, 1, n) hrp(j, 1, m)
{
bool ext = 0;
rep(d, 0, 4)
{
int x = i+dx[d], y = j+dy[d];
if (1 <= x && x <= n && 1 <= y && y <= m && w[x][y] == w[i][j]-1) ext = 1;
}
dp[i][j][0] = !ext;
}
for(auto v: uni) for(auto p: pos[v]) rep(d, 0, 4)
{
int i = p.first, j = p.second, x = i+dx[d], y = j+dy[d];
if (w[x][y] == v+1) hrp(k, 1, 4)
dp[x][y][min(3LL, k)] += dp[i][j][k-1], dp[x][y][min(3LL, k)] %= MOD;
}
int ans = 0;
hrp(i, 1, n) hrp(j, 1, m)
{
bool ext = 0;
rep(d, 0, 4)
{
int x = i+dx[d], y = j+dy[d];
if (1 <= x && x <= n && 1 <= y && y <= m && w[x][y] == w[i][j]+1) ext = 1;
}
if (!ext) ans += dp[i][j][3], ans %= MOD;
}
cout<<ans<<endl;
H - Prince and Princess
原题表述过于不清楚了,感觉只能靠猜
每个房间都住一个人。王子需要找出公主在哪个房间。有三种人,一种人只能说真话,一种人只能说假话,还有一种人可真可假。公主属于第一种人。这三种人分别有\(a,b,c\)个。王子可以问某个人三种问题:
- 你是谁
- 在房间\(x\)里的是谁
- 公主在哪个房间
求最坏情况下王子找出公主在哪个房间需要提问的最少次数,或者指出他并不能找出。
最关键的一点在于想通:无法判断一个人是哪种人。于是我们只能靠问问题3来得出答案。
因此当\(a>b+c\)时有解,当问了\(2(b+c)+1\)次问题3后,一定有至少\(b+c+1\)个答案能够指向公主房间。反之无解。
注意当\(a=1,b+c=0\)时,只有一个房间里面住着公主,此时答案为\(0\)。
int a, b, c;
cin>>a>>b>>c;
if (a > b+c) cout<<"YES"<<endl<<(a != 1)*(2*(b+c)+1)<<endl;
else cout<<"NO"<<endl;
K - Triangle
有人浪费评测资源套数据
给定坐标均为整数的点\(A,B,C\)构成一个三角形。给点\(D\),如果\(D\)不在\(\triangle ABC\)的边上输出-1
,否则输出在\(\triangle ABC\)的边上的点\(E\),使得\(DE\)平分\(\triangle ABC\)面积。
很显然这道题在数学上没有任何难度,上一个二维记几板子即可搞定。可惜我没有二维记几板子。
直接用double表示斜率有大问题,不建议这么写。
非常丑陋的代码:
const double eps = 1e-9; // 1e-9是推荐eps 不要开太大
double k[5];
bool at[5];
struct Point
{
double x, y;
} p[5];
bool eq(double a, double b) // -inf & inf, a=b=inf时fabs(a-b) <= eps不成立
{
return a == b || fabs(a-b) <= eps;
}
double dis(Point a, Point b)
{
return sqrt((b.x-a.x)*(b.x-a.x)+(b.y-a.y)*(b.y-a.y));
}
double slope(Point a, Point b)
{
return (b.y-a.y)/(b.x-a.x);
}
int T = next();
while(T--)
{
rep(i, 0, 4) cin>>p[i].x>>p[i].y, at[i] = 0;
int at = -1, point = -1;
rep(s, 0, 3)
{
int t = (s+1)%3;
k[s] = slope(p[s], p[t]);
if (eq(p[s].x, p[3].x) && eq(p[s].y, p[3].y)) point = s;
else if ((eq(k[s], slope(p[3], p[s])) || eq(k[s], slope(p[s], p[3])))
&& min(p[s].x, p[t].x) <= p[3].x && p[3].x <= max(p[s].x, p[t].x)
&& min(p[s].y, p[t].y) <= p[3].y && p[3].y <= max(p[s].y, p[t].y)) at = s;
}
if (point != -1) { cout<<(p[(point+1)%3].x+p[(point+2)%3].x)/2<<' '<<(p[(point+1)%3].y+p[(point+2)%3].y)/2<<endl; continue; }
if (at == -1) { cout<<-1<<endl; continue; }
double r1 = dis(p[at], p[3])/dis(p[(at+1)%3], p[3]), r2 = r1<1 ? (r1+1)/2 : (r1-1)/r1/2;
int u = (at+1+(r1>=1))%3, v = (u+1)%3;
double ans1 = p[u].x+r2*(p[v].x-p[u].x), ans2 = p[u].y+r2*(p[v].y-p[u].y);
cout<<fixed<<setprecision(12)<<ans1<<' '<<ans2<<endl;
}
B - Chessboard
更奇怪的题面出现了
给定\(n*m\)棋盘,求出有多少种方式粉刷整个棋盘,但是粉刷存在限制:
- 从任意一个起点开始粉刷,粉刷后只能向邻居移动或移出棋盘
- 不能重复粉刷某个格子
- 在粉刷时的任何时刻不能让粉刷后的任意两个格子在已粉刷格子上的最短路长度\(>\)这两个格子间的曼哈顿距离。
可以推出一个结论:最后一个被染色点在棋盘角上。
考虑当前已经粉刷了\(s*t\)的棋盘,现在尝试扩大这个棋盘为\((s+1)*t\):
对于原来\(s*t\)的方案,每个方案都对应了扩大后仅一种方案。显然还有其它方案没有考虑到,因此考虑\((s+1)*(t-1)\),容易发现加上这组对应的方案后\(s*t\)的方案就已经完备了。
因此\(f(n,m)=f(n-1,m)+f(n,m-1)(n,m \geq 3)\)。
对于\(n-1\)或\(m-1\)等于\(1\)的情况,需要翻倍计算。因为\(f(1,m)|f(n,1)=2(n,m \geq 2)\)只对应了\(2\)个角。也就是\(f(2,m)=2*f(1,m)+f(2, m-1)(m \geq 3)\)。
为了让计算式更加普遍一点,可以直接将\(f(1,m)|f(n,1)\)赋值为\(4\),这样就有了\(f(n,m)=f(n-1,m)+f(n,m-1)(n,m \geq 2)\)。
这个式子显然是杨辉三角,于是\(f(n,m)=4*C^{n-1}_{n+m-2}\)。
对于\(n=m=1\),特判\(f(n,m)=1\)。不知道为什么网上没有一篇博客一个代码写了这个显而易见的特判而是只判断了\(n|m=1\),也不知道为什么出题人没有叉这个显而易见的点。
对于\(n=1\)或\(m=1\),特判\(f(n,m)=2\)。
组合数直接用逆元算,这就不得不提到神犇@Lcyanstars提示我Lucas定理的局限性楽!
Lcyanstars Fan Club地位++
int fac[2*U];
int quickPow(int a, int b)
{
int ret = 1%MOD, t = a;
while(b)
{
if (b & 1) ret = ret*t%MOD;
t = t*t%MOD;
b >>= 1;
}
return ret;
}
int inv(int x)
{
return quickPow(x, MOD-2);
}
int C(int n, int m)
{
return fac[n]*inv(fac[m])%MOD*inv(fac[n-m])%MOD;
}
fac[1] = 1;
rep(i, 2, U) fac[i] = fac[i-1]*i%MOD;
int T = next();
while(T--)
{
int n, m;
cin>>n>>m;
if (n+m == 2) cout<<1<<endl;
else if (n == 1 || m == 1) cout<<2<<endl;
else cout<<4*C(n+m-2, n-1)%MOD<<endl;
}
J - Spy
是一个很板的二分图最大匹配,所以可以咕咕咕。
D,I,E,F
都扔todolist吃灰去