2020icpc上海部分题解
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
2020icpc上海部分题解B
题⽬⼤意
给你两张扫雷图A和B,你最多对B修改⌊MN
2⌋次,问是否能让B中的数字之和等于A。
解题思路
扫雷图中的数字等价于相邻的⾮雷格⼦与空⽩格⼦的对数,相当于⼀张⿊⽩⾊的图,很明显,⿊⽩颜⾊是相对的,即是交换颜⾊,相邻的⿊⽩颜⾊的对数也不会改变(就像把⿊⽩
对改成⽩⿊对那样)。
所以对⽐两个图中不同的块的个数,如果⼩于⌊MN
2⌋就直接输出A,否则输出A格⼦翻转之后的图。
代码
const int maxn = 1e3+10;
int n, m;
char g1[maxn][maxn], g2[maxn][maxn], g3[maxn][maxn];
int solve(char ga[maxn][maxn], char gb[maxn][maxn]) {
int cnt = 0;
for (int i = 1; i<=n; ++i)
for (int j = 1; j<=m; ++j)
if (ga[i][j]!=gb[i][j]) ++cnt;
return cnt;
}
int main() {
IOS;
cin >> n >>m;
for (int i = 1; i<=n; ++i) cin >> g1[i]+1;
for (int i = 1; i<=n; ++i) cin >> g2[i]+1;
for (int i = 1; i<=n; ++i)
for (int j = 1; j<=m; ++j) {
if (g1[i][j]=='.') g3[i][j] = 'X';
else g3[i][j] = '.';
}
if (solve(g1, g2)<=n*m/2) {
for (int i = 1; i<=n; ++i) cout << g1[i]+1 << endl;
}
else if (solve(g3, g2)<=n*m/2) {
for (int i = 1; i<=n; ++i) cout << g3[i]+1 << endl;
}
return 0;
}
C
题⽬⼤意
给你两个数X和Y,求下⾯式⼦
解题思路
可以考虑数位dp按位填数,i&j=0说明同⼀位不同1,⽽后⾯的取log相当于求i和j中最⾼的那个⼆进制位位数。
这样就转换成了⼀个⽐较板⼦的数位dp了,细节可以看注释。
代码
int a[33], b[33];
ll dp[33][2][2][2];
ll solve(int len, int lim1, int lim2, int tp) {
//tp 0:前⾯有1 1:前⾯没有1
if (len==-1) return 1;
if (dp[len][lim1][lim2][tp]!=-1) return dp[len][lim1][lim2][tp];
int up1 = lim1 ? a[len]:1;
//如果前⾯到上限了,只能取当前上限,否则填0和1都可,即当前上限是1
int up2 = lim2 ? b[len]:1;
ll res = 0;
for (int i = 0; i<=up1; ++i)
for (int j = 0; j<=up2; ++j) {
if (i&j) continue;
ll t;
if (tp&&(i||j)) t = len+1;
//如果前⾯没有1,当前有1,说明当前是最⾼位,计算log
else t = 1;
res = (res+solve(len-1, lim1&&i==up1, lim2&&j==up2, tp&&!i&&!j)*t%MOD)%MOD;
//lim&&i==up1,如果前⾯取到上限,并且当前也是上限,对于下⼀个来说才受限制,否则随便取
}
return dp[len][lim1][lim2][tp] = res;
}
int main() {
IOS;
int __; cin >> __;
while(__--) {
int x, y; cin >> x >> y;
int len = 0;
while(x||y) {
Processing math: 100%
b[len] = y&1;
x >>= 1;
y >>= 1;
++len;
}
clr(dp, 0xff);
cout << ((solve(len-1, 1, 1, 1)-1)%MOD+MOD)%MOD << endl;
//减1即减去0 0的情况
}
return 0;
}
D
题⽬⼤意
两⼈站在[0,n]的线段上,⼀个⼈站在p1,速度v1,另⼀个⼈站在p2,速度v2,问两个⼈最少需要多少时间使每个点⾄少被覆盖⼀遍。
解题思路
⽐赛时想着分三段三分相遇的位置,但是不确定函数是否单峰,⼀直wa个不停,其实可以⼆分时间,很明显这是有单调性的。
我们⼆分时间,然后计算两个⼈⾛的互不交叉并且各包含⼀个端点的最长路径,看能否把整个线段覆盖就⾏了。
但是这⾥还有⼀些坑,就看代码吧。
注意最后还要考虑上两⼈对穿的情况。
代码
const int maxn = 1e3+10;
int main() {
IOS;
int __; cin >> __;
while(__--) {
double n, p1, v1, p2, v2;
cin >> n >> p1 >> v1 >> p2 >> v2;
if(p1>p2) {
swap(p1,p2);
swap(v1,v2);
}
double l = 0, r = 1e12, ans = 0;
for (int i = 1; i<=200; ++i) {
double t = (l+r)/2;
double l1 = t*v1, l2 = t*v2;
l1 = max((l1+p1)/2, l1-p1);
if (l1<p1+eps) l1 = 0; //没贡献
l2 = max((l2+n-p2)/2, l2-(n-p2));
if (l2<n-p2) l2 = 0; //没贡献
if (l1+l2-n>=-eps) ans = t, r = t;
else l = t;
}
ans = min(ans, max(p2/v2, (n-p1)/v1));
cout << fixed << setprecision(10) << ans << endl;
}
return 0;
}
E
解题思路
从最⼩的数下⼿,如果最⼩的数放在位置i,那么i前⾯的i-1个数可以随便放,⽽最⼩值后⾯的数字,相当于⼀个新的排列,我们可以继续从最⼩的数开始划分,所以可以得到下⾯的状态转移⽅程:
dp[i]=
i−k
∑
j=i−1dp[i−j]×c(i−1,j−1)×!(j−1)
这是⼀个O(nk)的做法,我们把柿⼦变换⼀下
i−k
∑
j=i−1dp[i−j]×c(i−1,j−1)×!(j−1)
i−k
∑
j=i−1dp[i−j]×
!(i−1)
!(j−1)×!(i−j)×!(j−1)
!(i−1)×
i−k
∑
j=i−1
dp[i−j]
!(i−j)
我们可以⽤前缀和来存∑i j=1dp[j]
!j,通过前缀和做差求前k个数,这样的话就省去第⼆个循环的k次枚举,O(n)得到答案了。
const int maxn = 1e7+10; const int maxm = 1e6+10;
int f[maxn], inv[maxn], dp[maxn], sum[maxn];
ll qp(ll x, ll y) {
ll res = 1;
while(y) {
if (y&1) res = res*x%MOD;
x = x*x%MOD;
}
return res;
}
int main() {
IOS;
f[0] = 1;
for (int i = 1; i<maxn; ++i) f[i] = 1ll*f[i-1]*i%MOD;
inv[maxn-1] = qp(f[maxn-1], MOD-2);
for (int i = maxn-2; i>=0; --i) inv[i] = 1ll*(i+1)*inv[i+1]%MOD;
int n, k; cin >> n >> k;
dp[0] = sum[0] = 1;
for (int i = 1; i<=n; ++i) {
dp[i] = sum[i-1];
if (i-k-1>=0) dp[i] = (dp[i]-sum[i-k-1]+MOD)%MOD;
dp[i] = 1ll*dp[i]*f[i-1]%MOD;
sum[i] = (sum[i-1]+1ll*dp[i]*inv[i]%MOD)%MOD;
//cout << dp[i] << ' ' << sum[i] << endl;
}
cout << dp[n] << endl;
return 0;
}
G
签到,3的倍数的位置对应偶数,偶数不管乘奇数还是偶数都是偶数。
H
解题思路
结论应该是⽐较好猜的:我们把每个⼈和对应的⼿抓饭在圆盘上连线,那么这些连线是不会相交的。
相应的证明可以看。
这样的话⼀共有三种情况,要么顺时针转,要么逆时针转,要么先往⼀个⽅向转⼀个⾓度,再反过来转另⼀个⾓度。
我们可以分别对a和b排序,然后假设⼀个⽅向为正,枚举第⼀个⼈抓的饭或者抓第⼀个饭的⼈,计算出正⽅向每个⼈抓到饭所需的时间,那么正⽅向的时间就是最⼤的那个时间,反⽅向就是n-正⽅向最⼩的那个时间,然后再枚举下两个⽅向转时的分割点就⾏了。
代码
const int maxn = 2e5+10;
const int maxm = 1e6+10;
ll a[maxn], b[maxn], c[maxn];
int main() {
IOS;
int __; cin >> __;
while(__--) {
int n, m; cin >> n >> m;
for (int i = 0; i<m; ++i) cin >> a[i];
for (int i = 0; i<m; ++i) cin >> b[i];
sort(a, a+m);
sort(b, b+m);
ll ans = 1e18;
for (int i = 0; i<m; ++i) {
for (int j = 0; j<m; ++j) c[j] = (b[(j+i)%m]-a[j]+n)%n;
sort(c, c+m);
ans = min({ans, c[m-1], n-c[0]});
for (int j = 0; j<m-1; ++j) ans = min({ans, 2*c[j]+n-c[j+1], 2*(n-c[j+1])+c[j]});
}
cout << ans << endl;
}
return 0;
}
I
题⽬⼤意
给你n个同⼼圆(圆⼼0,0)已经n条直线(穿过圆⼼),求直线与圆相交的点之间的最⼩距离之和。
解题思路
对于两个在同⼀个圆上的点,他们要么是⾛所在圆的圆弧,要么往下⾛绕⼀圈再上来。
对于不再同⼀个圆上的点,⼀定是离中⼼最远的点先沿直径⾛到和另⼀个点所在的圆弧,然后就是两个点在同⼀个圆上的情况。
考虑第⼀种情况往下⾛绕⼀圈再上来的⾛法,假设⼀定经过若⼲段圆弧,那么⼀定存在两个点只⾛⼀个圆弧的情况,如下图
假设从A点下到红⾊圆弧再回到B是最短路,夹⾓相当于p×π
2×m,那么必有2×j>
p×2×π×j
2×m,化简得到2×m>π×p可以发现是⾛两条长度为j的边还是⾛圆弧和j的长度是没有关系的,只
和⾓度有关,⽽且实际⾛的圆弧应该是AB所在圆的,把j+1可以推出来圆弧越靠上越优,所以我们可以推出位于同⼀个圆上的两点只有两种⾛法,满⾜2×m>π×p⾛两点所在圆的圆弧,否则⾛半径。
我们可以枚举每层中的⼀个点到其他所有点的距离,根据对称性算出这⼀层的贡献,但是还有更快的⽅法。
我们可以根据层间关系来推,维护两个变量,⼀个是⽐现在低⼀层的那层中的⼀个点到层数⼩于等于它的所有点的距离res,⼀个是res对应的点的个数sum,我们要求当前这⼀层中的⼀个点到⽐他低的点的距离,就可以⽤res和sum算出来,相当于上⼀个结果中所有路径往上再⾛⼀步。
处理完层间的,我们再加上同层的就⾏了,可以根据上⾯推出来的结论,预处理出来同层之间需要乘上的系数再乘上半径和点数就可以了。
代码
int main() {
IOS;
int n, m; cin >> n >> m;
for (int i = 1; i<m; ++i) {
if (2.0*m>pi*i) t += pi*2.0*i/m;
else t += 4;
}
t += 2;
double ans = 0, res = 0, sum = 1;
for (int i = 1; i<=n; ++i) {
ans += t*i*m+2*m*(res+sum);
//t*i*m代表⾛同⼀层,res代表靠⾥那⼀层⼀个点到所有⼩于等于它的那⼀层的点的距离
//sum是从这⼀层的⼀个点下到⽐他⼩的层的点⾛⼀步需要多少步
res += t*i+sum;
//res从下⾯⼀层上来,⾸先加上sum个1,然后再加上同层的距离
sum += 2*m;
}
if (m==1) ans -= (1+n)*n*m;
cout << fixed << setprecision(10) << ans << endl;
return 0;
}
M
解题思路
先对可以忽略的⽂件建⼀棵字典树,要想使ignore的⽂件数量少,肯定在树上能ignore的最靠上的位置最好。
⽽能ignore的⽂件肯定不能包含不能ignore的⽂件,所以我们再把不能ignore的⽂件插⼊字典树中,把中间经过的点标记⼀下,然后我们对字典树dfs⼀下,⾛到未标记的点就+1然后不再往下⾛,最后就能得到答案。
代码
const int maxn = 1e3+10;
const int maxm = 2e6+10;
int tr[maxn][27], f[maxn], son[maxn], idx;
void insert(string t, int flag) {
int p = 0;
for (auto ch : t) {
++son[p];
int tt = ch-'a';
if (ch=='/') tt = 26;
if (!tr[p][tt]) tr[p][tt] = ++idx;
p = tr[p][tt];
//cout << p << endl;
f[p] = flag;
}
//cout << "________" << endl;
++son[p];
}
ll ans;
void dfs(int u) {
for (int i = 0; i<27; ++i)
if (tr[u][i]) {
if (i==26 && !f[tr[u][i]]) ++ans;
else dfs(tr[u][i]);
}
}
int main() {
IOS;
int __; cin >> __;
while(__--) {
idx = 0; f[0] = 1;
int n, m; cin >> n >> m;
for (int i = 1; i<=n; ++i) {
string s; cin >> s;
s += '/';
insert(s, 0);
}
for (int i = 1; i<=m; ++i) {
string s; cin >> s;
s += '/';
insert(s, 1);
}
ans = 0;
dfs(0);
cout << ans << endl;
for (int i = 0; i<=idx; ++i) son[i] = f[i] = 0, clr(tr[i], 0);
}
return 0;
}。