Preface
经典前期天胡开局,70min的时候就已经过6题+会另外3题了,本来以为又要像昨天那样提前下班了
后面好家伙FGH连着卡,最后磕磕碰碰刚好在结束前写完10个题,强行续上班时长了属于是
A. Gratitude
签到,注意出现次数相同的字符串的处理
#include<cstdio>
#include<iostream>
#include<string>
#include<map>
#include<utility>
#include<algorithm>
#include<vector>
#include<cctype>
#define RI register int
#define CI const int&
using namespace std;
typedef pair <int,int> pi;
struct ifo
{
pi fre; string name;
friend inline bool operator < (const ifo& A,const ifo& B)
{
return A.fre>B.fre;
}
}; int n,k; map <string,pi> mp;
int main()
{
//freopen("A.in","r",stdin); freopen("A.out","w",stdout);
ios::sync_with_stdio(false); cin.tie(0);
RI i; string s; getline(cin,s);
for (i=0;i<s.size();++i)
if (isdigit(s[i])) n=n*10+s[i]-'0'; else break;
for (++i;i<s.size();++i)
if (isdigit(s[i])) k=k*10+s[i]-'0'; else break;
for (i=1;i<=n*3;++i)
{
getline(cin,s);
++mp[s].first; mp[s].second=i;
}
vector <ifo> item;
for (auto [name,fre]:mp) item.push_back((ifo){fre,name});
sort(item.begin(),item.end());
for (i=0;i<min((int)item.size(),k);++i) cout<<item[i].name<<endl;
}
B. Rule 110
防AK题,弃疗
C. Safe Distance
祁神开场写的,好像是个二分+并查集判断连通性
#include<bits/stdc++.h>
#define int long long
using namespace std;
#define LD long double
const LD eps = 1e-8;
int sgn(LD x){return fabs(x)<=eps ? 0 : (x>eps ? 1 : -1);}
struct Pt{
LD x, y;
Pt operator-(Pt b)const{return Pt{x-b.x, y-b.y};}
LD len(){return sqrtl(x*x+y*y);}
};
const int N = 1e3+5;
int X, Y, n;
Pt pt[N];
LD dis[N][N];
int fa[N];
int gf(int x){return x==fa[x] ? x : fa[x]=gf(fa[x]);}
bool check(LD x){
for (int i=0; i<=n+1; ++i) fa[i]=i;
for (int i=0; i<=n+1; ++i){
for (int j=i+1; j<=n+1; ++j){
if (sgn(dis[i][j] - x)<0 && gf(i)!=gf(j)) fa[gf(i)] = gf(j);
}
}
// printf("x=%Lf\n", x);
// printf("fa:"); for (int i=0; i<=n+1; ++i) printf("%lld ", fa[i]); puts("");
return gf(0)==gf(n+1);
}
signed main(){
ios::sync_with_stdio(0); cin.tie(0);
cout << setiosflags(ios::fixed) << setprecision(6);
cin >> X >> Y;
cin >> n;
for (int i=1; i<=n; ++i) cin >> pt[i].x >> pt[i].y;
for (int i=1; i<=n; ++i){
dis[0][i] = dis[i][0] = min(pt[i].x, Y-pt[i].y);
dis[n+1][i] = dis[i][n+1] = min(X-pt[i].x, pt[i].y);
}
dis[0][n+1] = dis[n+1][0] = 1e18;
for (int i=1; i<=n; ++i){
for (int j=i+1; j<=n; ++j){
dis[i][j] = dis[j][i] = 0.5L*(pt[i]-pt[j]).len();
// printf("dis[%lld][%lld]=%Lf\n", i, j, dis[i][j]);
}
}
LD L=0, R=1e18;
while (sgn(R-L)>0){
// printf("L=%.20Lf R=%.20Lf R-L=%.20Lf R-L<eps=%d sgn(R-L)=%lld\n", L, R, R-L, R-L<eps, sgn(R-L));
LD M = 0.5L*(R+L);
if (check(M)) R=M;
else L=M;
}
cout << L << '\n';
return 0;
}
D. Jogging
乍一想感觉无从下手,但实际上由于一条边可以被经过任意时间,我们不妨考虑每次只经过新的一条边的情况
考虑对于两个端点为\(x,y\)的边,下限其实总是可以满足(因为可以在一条边上随便刷时间)
至于上限只要满足能走到即可,即判断\(2\times \min(dis_x,dis_y)<U\)
#include<cstdio>
#include<iostream>
#include<queue>
#include<utility>
#include<vector>
#define RI register int
#define CI const int&
using namespace std;
typedef pair <int,int> pi;
const int N=100005,INF=1e9;
int n,m,L,U,x[N],y[N],z[N],dis[N],vis[N]; vector <pi> v[N];
int main()
{
RI i; for (scanf("%d%d%d%d",&n,&m,&L,&U),i=1;i<=m;++i)
{
scanf("%d%d%d",&x[i],&y[i],&z[i]); ++x[i]; ++y[i];
v[x[i]].push_back(pi(y[i],z[i])); v[y[i]].push_back(pi(x[i],z[i]));
}
for (i=1;i<=n;++i) dis[i]=INF;
priority_queue <pi> hp; hp.push(pi(dis[1]=0,1));
while (!hp.empty())
{
int now=hp.top().second; hp.pop();
if (vis[now]) continue; vis[now]=1;
for (auto [to,w]:v[now]) if (dis[to]>dis[now]+w)
hp.push(pi(-(dis[to]=dis[now]+w),to));
}
int ans=0; for (i=1;i<=m;++i)
if (min(dis[x[i]],dis[y[i]])*2<U) ++ans;
return printf("%d",ans),0;
}
E. Cakes
签到题,祁神开场写的,我题意都没看
#include<bits/stdc++.h>
#define int long long
using namespace std;
signed main(){
int n; cin >> n;
int ans=1e9+5;
for (int i=1; i<=n; ++i){
int a, b; cin >> a >> b;
ans = min(ans, b/a);
}
cout << ans << '\n';
return 0;
}
F. Mentors
感觉是个挺难的DP题,不知道怎么中期就被过穿了
首先考虑没有\(R\)为叶子节点的限制时,我们可以轻松DP求出由\(i\)个点构成的合法的二叉树的数量,设为\(g_i\)
转移的话分别考虑有一个/两个儿子的情况,然后枚举一下其中一个子树的大小即可
现在考虑加上\(R\)为叶子节点的限制,刚开始想的是直接设\(f_i\)表示有\(i\)个点且\(R\)为叶子节点的方案数,但后面发现不太对劲
仔细一想需要把\(R\)在这\(i\)个点中的相对大小加进去,于是令\(f_{i,j}\)表示有\(i\)个点,\(R\)在这些点中为第\(j\)小且为叶子节点的方案数
转移的话从小到大枚举\(rk\),那么可以用到的点\(m=rk+n-R\),考虑用容斥减去\(R\)不为叶子的方案数
\[f_{m,rk}=g_m-\sum_{j=1}^{rk} C_{rk-1}^{j-1}\times g_j\times f_{m-j+1,rk-j+1} \]总复杂度\(O(n^2)\)
#include <bits/stdc++.h>
using llsi = long long signed int;
constexpr int $n = 2024;
llsi r, n, mod;
llsi C[$n][$n], g[$n], f[$n][$n];
inline int plus(int a, int b) {
int c = a + b;
if(c >= mod) c -= mod;
return c;
}
int main() {
std::cin >> r >> n >> mod;
for(int i = 0; i <= n; ++i) C[i][0] = 1;
for(int i = 1; i <= n; ++i) for(int j = 1; j <= i; ++j)
C[i][j] = plus(C[i - 1][j], C[i - 1][j - 1]);
g[0] = g[1] = 1;
for(int i = 2; i <= n; ++i) {
g[i] = g[i - 1];
for(int k = 1; k < i - 1; ++k)
g[i] += g[k] * g[i - k - 1] % mod * C[i - 2][k - 1] % mod;
g[i] %= mod;
}
f[1][n - r + 1] = g[n - r + 1];
for(int rk = 2; rk <= r; ++rk) {
int NN = rk + n - r;
f[rk][NN] = g[NN];
for(int j = 2; j <= rk; ++j)
f[rk][NN] += mod - C[rk - 1][j - 1] * g[j] % mod * f[rk - j + 1][NN - j + 1] % mod;
f[rk][NN] %= mod;
}
// for(int i = 1; i <= n; ++i) std::cerr << g[i] << char(i == n ? 10 : 32);
std::cout << f[r][n] << std::endl;
return 0;
}
G. Decoration
思路很好想,但写起来比较坐牢的题,最后祁神还被一个Corner Case卡了2h
首先不难发现当\(s_1\)确定后整个序列就唯一确定了,同时每个数都有唯一的后继(除了\(0\)之外)
因此如果把图建出来后得到的就是个基环外向森林,要得到从一个点出发的向后的数的个数以及至多\(k\)个数的和,我们只要在上面跑记搜即可
这里祁神实现的时候用了倍增,实际上我yy了一下应该可以不用,但是无伤大雅
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N = 1e6+5;
int n, k;
int pri[N], d[N], totp=0;
bool isP[N];
int stk[N], top=-1;
bool inStk[N];
int dp[N], g[N];
int nxt[N][22], nxtk[N];
void dfs(int x){
// printf("dfs(%lld)\n", x);
if (0==x){
dp[x]=1;
return;
}
inStk[x]=true;
stk[++top]=x;
int v = (x+d[x])%n;
if (dp[v]>0){
dp[x] = dp[v]+1;
g[x] = g[v]+x;
if (dp[x]>k) g[x]-=nxtk[x];
}else if (inStk[v]){
vector<int> vec;
int sum=0;
while (top>=0 && stk[top]!=v){
// printf("stk[top]=%lld\n", stk[top]);
vec.push_back(stk[top]);
sum+=stk[top];
inStk[stk[top]]=false, --top;
}
vec.push_back(v);
sum+=v;
inStk[stk[top]]=false; --top;
int sz=vec.size();
if (sz<k){
for (int w : vec) dp[w]=sz, g[w]=sum;
}else{
for (int w : vec) dp[w]=sz;
sum=0;
int frt=vec[0]; int cur=vec[0];
for (int i=0; i<k; ++i){
sum+=cur;
cur=nxt[cur][0];
}
g[frt]=sum;
for (int i=1; i<sz; ++i){
sum+=cur;
cur=nxt[cur][0];
sum-=frt;
frt=nxt[frt][0];
g[frt]=sum;
}
}
// printf("vec:"); for (int w : vec) printf("w=%lld dp[w]=%lld g[w]=%lld\n", w, dp[w], g[w]); puts("");
}else{
dfs(v);
if (!dp[x]){
dp[x]=dp[v]+1;
g[x]=g[v]+x;
if (dp[x]>k) g[x]-=nxtk[x];
}
}
if (inStk[x]) inStk[x]=false, --top;
}
signed main(){
ios::sync_with_stdio(0); cin.tie(0);
d[1]=1; d[0]=1;
for (int i=2; i<N; ++i){
if (!isP[i]) pri[++totp]=i, d[i]=2;
for (int j=1; j<=totp&&i*pri[j]<N; ++j){
isP[i*pri[j]] = true;
if (i%pri[j]){
d[i*pri[j]] = d[i]*2;
}else{
d[i*pri[j]] = d[i]*2 - d[i/pri[j]];
break;
}
}
}
// printf("d[162]=%lld\n", d[162]);
cin >> n >> k;
nxt[0][0]=0;
for (int i=1; i<n; ++i) nxt[i][0] = (i+d[i])%n;
for (int j=1; j<=20; ++j){
for (int i=0; i<n; ++i){
nxt[i][j] = nxt[nxt[i][j-1]][j-1];
}
}
for (int i=0; i<n; ++i){
int cur=i;
for (int j=0; j<=20; ++j){
if (k&(1LL<<j)) cur=nxt[cur][j];
}
nxtk[i] = cur;
}
for (int i=0; i<n; ++i){
if (dp[i]==0) dfs(i);
}
// printf("dp:"); for (int i=0; i<n; ++i) printf("%lld ", dp[i]); puts("");
// printf("g:"); for (int i=0; i<n; ++i) printf("%lld ", g[i]); puts("");
// printf("nxtk:"); for (int i=0; i<n; ++i) printf("%lld ", nxtk[i]); puts("");
int ans=-1;
for (int i=0; i<n; ++i){
if (dp[i]>=k){
if (ans==-1) ans=i;
else if (g[ans]>g[i]) ans=i;
}
}
if (-1==ans) cout << "-1\n";
else{
for (int x=ans, j=1; j<=k; ++j, x=(x+d[x])%n){
cout << x << ' ';
}cout << '\n';
}
return 0;
}
H. Figurines
徐神写的,好像是个可持久化线段树板子题,我题目都没看
#include <bits/stdc++.h>
constexpr int $n = 100000;
namespace smt {
constexpr int $node = $n * 50;
int lc[$node] = {0}, rc[$node] = {0}, val[$node] = {0}, O = 1;
int add(int cur, int pos, int v, int L, int R) {
int res = O++;
val[res] = val[cur] + v;
if(L == R) return lc[res] = rc[res] = 0, res;
int M = (L + R) >> 1;
if(pos > M) lc[res] = lc[cur], rc[res] = add(rc[cur], pos, v, M + 1, R);
else rc[res] = rc[cur], lc[res] = add(lc[cur], pos, v, L , M);
return res;
}
int query(int cur, int l, int r, int L, int R) {
if(!cur || R < l || r < L) return 0;
if(l <= L && R <= r) return val[cur];
int M = (L + R) >> 1;
return
query(lc[cur], l, r, L , M) +
query(rc[cur], l, r, M + 1, R);
}
}
int n = 0, x = 0, ver[$n + 1] = {0};
std::string s;
int main() {
std::ios::sync_with_stdio(false);
std::getline(std::cin, s);
{
std::stringstream ss;
ss << s; ss >> n;
}
for(int i = 1; i <= n; ++i) {
ver[i] = ver[i - 1];
std::getline(std::cin, s);
int p = 0, ss = s.size();
while(p < ss) {
int pos = 0, v;
while(p < ss && !std::isdigit(s[p])) {
if(s[p] == '+') v = 1;
if(s[p] == '-') v = -1;
p += 1;
}
if(p >= ss) break;
while(p < ss && std::isdigit(s[p]))
pos = pos * 10 + (s[p++] ^ 48);
ver[i] = smt::add(ver[i], pos, v, 0, n - 1);
}
}
for(int i = 0; i < n; ++i) {
int d; std::cin >> d;
// std::cerr << "debug " << smt::val[ver[d]] << char(10);
x = x + smt::query(ver[d], x, n - 1, 0, n - 1);
if(x >= n) x -= n;
//std::cerr << "x = " << x << char(10);
}
std::cout << x << std::endl;
return 0;
}
I. Emails
好玄学的题啊
首先手玩一下会发现只要图连通就必定有解,同时若两点间距离为\(D\),则要让这两点通信需要的时间为\(\lceil\log_2 D\rceil\)
于是问题转化为求图的直径,但众所周知这是个经典难问题,没有什么比较好的做法
不过万幸的是仔细读题会发现允许我们输出的答案有\(1\)的误差,那么我们可以利用经典结论
随便找一个点,设从它出发走到最远的点的距离为\(E\),则\(2E\ge D\),其中\(D\)为图的直径
那么我们只要随便求出一个\(E\),输出\(\lceil\log_2 E\rceil+1\)即可
#include<cstdio>
#include<iostream>
#include<queue>
#include<vector>
#include<cmath>
#define RI register int
#define CI const int&
using namespace std;
typedef pair <int,int> pi;
const int N=100005;
int n,m,x,y,dis[N]; vector <int> v[N];
int main()
{
RI i; for (scanf("%d%d",&n,&m),i=1;i<=m;++i)
scanf("%d%d",&x,&y),v[x].push_back(y),v[y].push_back(x);
for (i=1;i<=n;++i) dis[i]=-1; dis[1]=0;
queue <int> q; q.push(1);
while (!q.empty())
{
int now=q.front(); q.pop();
for (auto to:v[now]) if (dis[to]==-1) dis[to]=dis[now]+1,q.push(to);
}
for (i=1;i<=n;++i) if (dis[i]==-1) return puts("-1"),0;
int mx=0; for (i=1;i<=n;++i) mx=max(mx,dis[i]);
for (x=1,y=0;x<mx;x*=2,++y);
return printf("%d",y+1),0;
}
J. Daisy's Mazes
题目都没看,弃疗
K. Unique Activities
徐神教我的string题
答案显然具有二分性,而检验可以用Hash来处理,注意实现的常数(这里用了unordered_map
)
#include<cstdio>
#include<iostream>
#include<cstring>
#include<unordered_map>
#define RI register int
#define CI const int&
using namespace std;
typedef long long LL;
const int N=300005,mod1=998244353,mod2=1e9+7;
struct Hasher
{
int x,y;
inline Hasher(CI X=0,CI Y=0)
{
x=X; y=Y;
}
inline LL val(void)
{
return ((1LL*x)<<31LL)|(1LL*y);
}
friend inline bool operator == (const Hasher& A,const Hasher& B)
{
return A.x==B.x&&A.y==B.y;
}
friend inline Hasher operator + (const Hasher& A,const Hasher& B)
{
return Hasher((A.x+B.x)%mod1,(A.y+B.y)%mod2);
}
friend inline Hasher operator - (const Hasher& A,const Hasher& B)
{
return Hasher((A.x-B.x+mod1)%mod1,(A.y-B.y+mod2)%mod2);
}
friend inline Hasher operator * (const Hasher& A,const Hasher& B)
{
return Hasher(1LL*A.x*B.x%mod1,1LL*A.y*B.y%mod2);
}
}h[N],pw[N];
const Hasher seed=Hasher(31,131);
int n,st; char s[N];
inline Hasher get(CI l,CI r)
{
return h[r]-h[l-1]*pw[r-l+1];
}
inline bool check(CI x)
{
RI i; unordered_map <LL,int> bkt;
for (i=1;i+x-1<=n;++i) ++bkt[get(i,i+x-1).val()];
for (i=1;i+x-1<=n;++i) if (bkt[get(i,i+x-1).val()]==1) return st=i,1;
return 0;
}
int main()
{
RI i; for (scanf("%s",s+1),n=strlen(s+1),i=1;i<=n;++i)
h[i]=h[i-1]*seed+Hasher(s[i],s[i]);
for (pw[0]=Hasher(1,1),i=1;i<=n;++i) pw[i]=pw[i-1]*seed;
int l=1,r=n,mid,len; while (l<=r)
if (check(mid=l+r>>1)) len=mid,r=mid-1; else l=mid+1;
for (check(len),i=st;i<=st+len-1;++i) putchar(s[i]);
return 0;
}
L. Restaurants
什么魔改版稳定婚姻问题,直接上魔改版匈牙利艹过去
用一个队列维护所有还未匹配的人,每次取出队首并找出其还未尝试过匹配的优先级最高的餐厅
同时每个餐厅用堆维护一个待匹配优先级序列,如果出现需要替换掉原来的匹配的情况就类似于找到增广路模拟一下退回操作即可
复杂度不清楚,感觉是\(O(E\log E)\)级别的,其中\(E\)为边数
#include<cstdio>
#include<iostream>
#include<map>
#include<queue>
#include<algorithm>
#define RI register int
#define CI const int&
using namespace std;
const int N=50005;
int n,m,sz[N],x,match[N]; map <int,int> val[N],fr[N];
vector <int> v[N]; priority_queue <int> hp[N];
int main()
{
RI i; for (scanf("%d%d",&n,&m),i=1;i<=m;++i) scanf("%d",&sz[i]);
for (i=1;i<=n;++i)
{
char ch; do
{
scanf("%d%c",&x,&ch);
v[i].push_back(x);
} while (ch==' ');
reverse(v[i].begin(),v[i].end());
}
for (i=1;i<=m;++i)
{
char ch; int rk=0; do
{
scanf("%d%c",&x,&ch);
++rk; val[i][x]=rk; fr[i][rk]=x;
} while (ch==' ');
}
queue <int> q; for (i=1;i<=n;++i) q.push(i);
while (!q.empty())
{
int now=q.front(); q.pop();
while (!v[now].empty())
{
int to=v[now].back(),rk=val[to][now];
if (hp[to].size()<sz[to])
{
hp[to].push(rk); match[now]=to; break;
}
if (rk<hp[to].top())
{
int tmp=fr[to][hp[to].top()]; hp[to].pop();
hp[to].push(rk); match[now]=to; match[tmp]=0; q.push(tmp); break;
}
v[now].pop_back();
}
}
for (i=1;i<=n;++i) if (match[i]) printf("%d\n",i);
return 0;
}
M. Fantasmagorie
啥玩意题目都看不懂
Postscript
感觉现在成为前期战神了,后期没啥用只能给队友打打辅助
标签:std,const,Contest,int,top,Regional,2020,include,define From: https://www.cnblogs.com/cjjsb/p/17987997