The 2020 ICPC Asia Shanghai Regional Contest
Preface
开启狂暴(存疑)加训模式,国内近些年来的比赛基本都打过了,翻了半天翻出来一场 20 年的上海
这场总体打的还算挺顺,虽然中期唐了 E 题推了个错的式子扔给徐神优化成功卡了徐神半小时,但好在后面写了个暴力发现式子推错马上改出来了
后期两个题没有证明全是猜测,大力乱冲结果都过了,这就是 guess 之力啊
B. Mine Sweeper II
队友开场秒的,我题目都没看
#include<bits/stdc++.h>
using namespace std;
const int N = 1005;
int n, m;
string A[N], B[N];
signed main() {
ios::sync_with_stdio(0); cin.tie(0);
cin >> n >> m;
for (int i=0; i<n; ++i) cin >> A[i];
for (int i=0; i<n; ++i) cin >> B[i];
int cnt = 0;
for (int i=0; i<n; ++i) for (int j=0; j<m; ++j) if (A[i][j]!=B[i][j]) ++cnt;
if (cnt > n*m/2) for (int i=0; i<n; ++i) for (int j=0; j<m; ++j) A[i][j] = (A[i][j]=='.' ? 'X' : '.');
for (int i=0; i<n; ++i) cout << A[i] << '\n';
return 0;
}
C. Sum of Log
很典的数位 DP,要计算贡献只要记录 highbit 即可,单组数据复杂度 \(O(\log X\log Y)\)
但这题在 QOJ 上很卡常,最后换了读优才过
#include <bits/stdc++.h>
using llsi = long long signed int;
llsi dp[30][30][2][2];
llsi X, Y;
#define Tp template <typename T>
class FileInputOutput
{
private:
static const int S=1<<21;
#define tc() (A==B&&(B=(A=Fin)+fread(Fin,1,S,stdin),A==B)?EOF:*A++)
char Fin[S],*A,*B;
public:
Tp inline void read(T& x)
{
x=0; char ch; while (!isdigit(ch=tc()));
while (x=(x<<3)+(x<<1)+(ch&15),isdigit(ch=tc()));
}
#undef tc
}F;
llsi dfs(int cur, int high, int x_limit, int y_limit) {
if(cur < 0) {
// if(debug) std::cerr << "debug " << high << char(10);
return high + 1;
}
if(high >= 0 && dp[cur][high][x_limit][y_limit] >= 0) {
// if(debug) std::cerr << "use cache dp["<< cur << "][" << high << "]\n";
return dp[cur][high][x_limit][y_limit];
}
llsi ans = 0;
for(int x = 0; x <= (x_limit ? (X >> cur & 1) : 1); ++x) {
for(int y = 0; y <= (y_limit ? (Y >> cur & 1) : 1); ++y) {
if(x && y) continue;
int nxl = x_limit && (x == (X >> cur & 1));
int nyl = y_limit && (y == (Y >> cur & 1));
int nh = (high >= 0 ? high : (x || y) ? cur : -1);
ans += dfs(cur - 1, nh, nxl, nyl);
}
}
ans %= 1'000'000'007;
if(high >= 0) dp[cur][high][x_limit][y_limit] = ans;
return ans;
}
void work() {
F.read(X); F.read(Y);
memset(dp, -1, sizeof dp);
std::cout << dfs(30, -1, 1, 1) << char(10);
}
int main() {
// freopen("4.in", "r", stdin);
// freopen("4.out", "w", stdout);
int T; F.read(T); while(T--) work();
return 0;
}
D. Walker
小清新分讨题,想清楚其实就三种情况:
- 其中一个人全走了;
- 两个人面对面走到端点处停止;
- 存在一个分界点,使得两个人各种走分界点两侧的部分;
前两种情况都很 trivial,第三种情况需要考虑每个人先往哪个方向更优
一个容易的实现是先二分一个答案,确定了这个值后就很容易列式子求出每个人往哪个方向更优
#include <bits/stdc++.h>
int _;
double solve() {
double n, p1, v1, p2, v2;
_ = scanf("%lf%lf%lf%lf%lf", &n, &p1, &v1, &p2, &v2);
if(p1 > p2) std::swap(p1, p2), std::swap(v1, v2);
p2 = n - p2;
double ans = std::min({
(n + p1) / v1,
(n + n - p1) / v1,
(n + p2) / v2,
(n + n - p2) / v2,
std::max((n - p2) / v2, (n - p1) / v1),
});
auto check = [&](double T) {
if(T * v1 < p1 || T * v2 < p2) return false;
double l1 = std::max(p1 + (T - p1 / v1) * (0.5 * v1), p1 + (T - 2 * p1 / v1) * v1);
double l2 = std::max(p2 + (T - p2 / v2) * (0.5 * v2), p2 + (T - 2 * p2 / v2) * v2);
return l1 + l2 >= n;
};
double l = 0, r = 1.0e8;
for(_ = 0; _ < 200; ++_) {
double mid = (l + r) / 2.0;
if(check(mid)) r = mid;
else l = mid;
}
return std::min(ans, l);
}
int main() {
int T; _ = scanf("%d", &T); while(T--) printf("%.10lf\n", solve());
return 0;
}
E. The Journey of Geor Autumn
感觉想到就很简单的 Counting,优化的部分属于是看一眼就会,但有人又整烂荔枝祸害队友了我不说是谁
考虑设 \(f_i\) 表示长度为 \(i\) 的排列的答案,考虑怎么转移
首先要发现 \(1\) 只能放在 \([1,k]\) 的位置中,否则一定无解;我们考虑枚举 \(1\) 的位置 \(j\),显然 \([1,j-1]\) 这些位置可以随便填
考虑后面的部分在重标号后其实就变为了一个规模为 \(i-j\) 的子问题,因此有转移式:
把式子稍微变个形,令 \(g_i=\frac{f_i}{i!}\),则:
直接前缀和递推即可做到 \(O(n)\)
#include<cstdio>
#include<iostream>
#define RI register int
#define CI const int&
using namespace std;
const int N=1e7+5,mod=998244353;
int n,k,g[N],sg[N],fact[N],ifac[N],inv[N];
inline int quick_pow(int x,int p=mod-2,int mul=1)
{
for (;p;p>>=1,x=1LL*x*x%mod) if (p&1) mul=1LL*mul*x%mod; return mul;
}
inline void init(CI n)
{
fact[0]=1; for (RI i=1;i<=n;++i) fact[i]=1LL*fact[i-1]*i%mod;
ifac[n]=quick_pow(fact[n]); for (RI i=n-1;i>=0;--i) ifac[i]=1LL*ifac[i+1]*(i+1)%mod;
inv[0]=1; for (RI i=1;i<=n;++i) inv[i]=1LL*ifac[i]*fact[i-1]%mod;
}
int main()
{
scanf("%d%d",&n,&k); init(n);
for (RI i=1;i<=k;++i) g[i]=1,sg[i]=(sg[i-1]+g[i])%mod;
for (RI i=k+1;i<=n;++i)
{
g[i]=1LL*(sg[i-1]-sg[i-k-1]+mod)*inv[i]%mod;
sg[i]=(sg[i-1]+g[i])%mod;
}
return printf("%d",1LL*g[n]*fact[n]%mod),0;
}
G. Fibonacci
斐波那契的奇偶性有 \(3\) 的周期,因此直接统计下奇数偶数的个数即可
#include<cstdio>
#include<iostream>
#define RI register int
#define CI const int&
using namespace std;
int n;
int main()
{
scanf("%d",&n); int odd=n-n/3;
printf("%lld",1LL*n*(n-1)/2LL-1LL*odd*(odd-1)/2LL);
return 0;
}
H. Rice Arrangement
神秘猜结论,考虑最后一定存在一个最优解,满足将每个人和对应的食物连边后,线段不会交叉
因此直接大力枚举第一个人对应的食物(从顺时针/逆时针两个方向),然后剩下的就往另一个方向一一匹配,很容易直接计算答案
总复杂度 \(O(n^2)\)
#include<cstdio>
#include<iostream>
#include<algorithm>
#define RI register int
#define CI const int&
using namespace std;
const int N=1005;
int t,n,k,a[N],b[N];
int main()
{
for (scanf("%d",&t);t;--t)
{
scanf("%d%d",&n,&k); int ans=2e9;
for (RI i=1;i<=k;++i) scanf("%d",&a[i]);
for (RI i=1;i<=k;++i) scanf("%d",&b[i]),b[i]-=n;
sort(a+1,a+k+1); sort(b+1,b+k+1);
for (RI i=1;i<=2*k+1;++i)
{
int l=0,r=0;
for (RI j=1;j<=k;++j)
l=min(l,a[j]-b[j]),r=max(r,a[j]-b[j]);
ans=min(ans,-l+r+min(-l,r));
int tmp=b[1]; for (RI j=1;j<k;++j) b[j]=b[j+1]; b[k]=tmp+n;
}
printf("%d\n",ans);
}
return 0;
}
I. Sky Garden
祁神写的神秘几何,我题目都没看
#include <bits/stdc++.h>
using namespace std;
using LD = long double;
constexpr LD PI = acosl(-1.0L);
constexpr LD eps = 1e-9;
int sgn(LD x) {return fabs(x)<=eps ? 0 : (x>eps ? 1 : -1);}
int main() {
std::ios::sync_with_stdio(false); cin.tie(0);
cout << setiosflags(ios::fixed) << setprecision(10);
int n, m;
cin >> n >> m;
LD ans = 0;
LD th = PI / m;
if (m>1) {
for (int r1=1; r1<=n; ++r1) {
ans += 2*m*r1;
}
}
// printf("ans=%Lf\n", ans);
for (int r1=1; r1<=n; ++r1) {
LD res = 0.0L;
int cnt = 0;
LD arc = 0.0L;
for (int i=1; sgn(i*th-2) < 0; ++i) {
arc += th*i*r1;
++cnt;
}
LD tmp = arc*2 + (((m-cnt)*2-1)*r1*2);
// printf("tmp=%Lf\n", tmp);
res += tmp / 2;
for (int r2=r1+1; r2<=n; ++r2) {
res += (r2-r1)*2*m + tmp;
}
res *= 2*m;
ans += res;
}
cout << ans << '\n';
return 0;
}
L. Traveling in the Grid World
神秘猜结论,如果不能一步走到的话感觉两步显然是最优的
考虑在此基础上怎么算答案,朴素的想法是枚举 \((0,0)\to (p,q)\to(n,m)\),需要满足 \(\gcd(p,q)=\gcd(n-p,m-q)=1\and (p,q)\ne (n-p,m-q)\),但这样复杂度一眼寄
考虑优化,不难发现 \(\frac{p}{q}\) 越接近 \(\frac{n}{m}\) 答案就越小,因此我们枚举 \(p\),然后令 \(q\) 在 \(\frac{p\times m}{n}\) 附近找几个值即可,实测找两个都能过
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cmath>
#include<cassert>
#define int long long
#define RI register int
#define CI const int&
using namespace std;
int t,n,m;
signed main()
{
for (scanf("%lld",&t);t;--t)
{
scanf("%lld%lld",&n,&m);
int g=__gcd(n,m);
auto dist=[&](CI x,CI y)
{
return sqrt(1LL*x*x+1LL*y*y);
};
if (g==1) { printf("%.15lf\n",dist(n,m)); continue; }
double ans=1e18;
for (RI p=1;p<=n;++p)
{
int q=1LL*m*p/n;
if (__gcd(p,q)!=1||__gcd(n-p,m-q)!=1||(p==n-p&&q==m-q))
{
//do nothing
} else ans=min(ans,dist(p,q)+dist(n-p,m-q));
for (RI j=0;j<3;++j)
{
--q; if (q<0) break;
if (__gcd(p,q)!=1||__gcd(n-p,m-q)!=1||(p==n-p&&q==m-q))
{
//do nothing
} else ans=min(ans,dist(p,q)+dist(n-p,m-q));
}
}
for (RI q=1;q<=m;++q)
{
int p=1LL*n*q/m;
if (__gcd(p,q)!=1||__gcd(n-p,m-q)!=1||(p==n-p&&q==m-q))
{
//do nothing
} else ans=min(ans,dist(p,q)+dist(n-p,m-q));
for (RI j=0;j<3;++j)
{
--p; if (p<0) break;
if (__gcd(p,q)!=1||__gcd(n-p,m-q)!=1||(p==n-p&&q==m-q))
{
//do nothing
} else ans=min(ans,dist(p,q)+dist(n-p,m-q));
}
}
printf("%.15lf\n",ans);
}
return 0;
}
M. Gitignore
字符串处理+简单 DP
不难发现把文件树建出来后如果一个节点内的所有叶子都要删除,则其贡献为 \(1\);否则为所有儿子的贡献之和
#include<cstdio>
#include<iostream>
#include<string>
#include<map>
#include<vector>
#include<algorithm>
#define RI register int
#define CI const int&
using namespace std;
const int N=1e6+5;
int t,n,m,idx,f[N],ext[N]; map <string,int> rst; vector <int> v[N];
inline int ID(const string& s)
{
if (rst.count(s)) return rst[s];
return rst[s]=++idx;
}
inline void DFS(CI now=1)
{
for (auto to:v[now]) DFS(to),ext[now]|=ext[to];
if (!ext[now]) f[now]=1; else
{
f[now]=0;
for (auto to:v[now]) f[now]+=f[to];
}
}
int main()
{
ios::sync_with_stdio(0); cin.tie(0);
for (cin>>t;t;--t)
{
cin>>n>>m; idx=1;
for (RI i=1;i<=n;++i)
{
string s,tmp; int now=1;
cin>>s; s+="/";
for (RI j=0;j<s.size();++j)
{
if (s[j]=='/')
{
v[now].push_back(ID(tmp));
now=ID(tmp);
}
tmp+=s[j];
}
}
for (RI i=1;i<=m;++i)
{
string s,tmp; int now=1;
cin>>s; s+="/";
for (RI j=0;j<s.size();++j)
{
if (s[j]=='/')
{
v[now].push_back(ID(tmp));
now=ID(tmp);
}
tmp+=s[j];
}
ext[now]=1;
}
for (RI i=1;i<=idx;++i)
{
sort(v[i].begin(),v[i].end());
v[i].erase(unique(v[i].begin(),v[i].end()),v[i].end());
}
// for (RI i=1;i<=idx;++i)
// {
// cout<<"i = "<<i<<": ";
// for (auto x:v[i]) cout<<x<<" ";
// cout<<'\n';
// }
DFS(); int ans=0;
for (auto x:v[1]) ans+=f[x];
cout<<ans<<'\n';
for (RI i=1;i<=idx;++i) v[i].clear(),ext[i]=0; rst.clear();
}
return 0;
}
Postscript
感觉现在打比赛就看一手前期啊,前期顺后面就越打越好,前期烂了后面就疯狂红温

浙公网安备 33010602011771号