XJTUPC 2024 题解&游记
AC 代码等赛后代码公开之后放上来。
A
弱智题,直接输出
B
弱智题,直接输出
C
弱智题,直接根据公式计算即可。
D
弱智题,容易发现一条链正着做反着做总有一个符合条件,输出即可。
#include <bits/stdc++.h>
using namespace std;
int main()
{
int n,m,u,v;
int res=0;
scanf("%d %d",&n,&m);
while(m--)
{
scanf("%d %d",&u,&v);
if(u<=v) ++res;
else --res;
}
if(res>=0)
{
for(int i=0;i<n;++i) printf("%d%c",i," \n"[i==n-1]);
}
else
{
for(int i=2;i<=n;++i) printf("%d ",i); puts("0");
}
}
E
签到题,考虑增量构造,每次把
接下来我们证明这么做是对的:
既然左边的房子都已经构造好了。那么右边的房子不影响左边的答案,因此可以增量构造。
考虑
接下来考虑实现,可以直接链表或者并查集维护。赛时采用了并查集,但是似乎链表更简单?
#include <bits/stdc++.h>
int fa[222222];
int gf(int u){
if(fa[u]==u){
return u;
}
return fa[u]=gf(fa[u]);
}
int a[222222];
int next[222222];
int main()
{
int n;
scanf("%d",&n);
for(int i=1;i<=n;++i){
scanf("%d",a+i);
}
for(int i=1;i<=n;++i){
fa[i]=i;
}
for(int i=n;i;--i){
int x=gf(a[i]);
next[x]=i;
fa[x]=i;
}
int k=n;
while(a[k]!=0)--k;
while(k){
printf("%d ",k);
k=next[k];
}
return 0;
}
F
弱智题。
用桶维护即可。
#include<bits/stdc++.h>
long long sum[2222222];
int main(){
int n,m;
scanf("%d%d",&n,&m);
for(int i=1;i<=n;++i){
int a;
scanf("%d",&a);
sum[a]+=i;
}
for(int i=1;i<=m;++i){
int x,y;
scanf("%d%d",&x,&y);
printf("%lld\n",sum[x]*sum[y]);
}
return 0;
}
G
较难题,赛时没搞出来。
首先只要求出
具体地,注意到
考虑拆位计算
就可以直接处理卷积结果了。
inline void convovle(const int *f,int m,int d,ll *R){ // 对于序列 f 计算它和参数为 d 时的序列 b 卷积
static ll g[N];
memset(g,0,sizeof(g));
for(int i=(1<<d);i<2*m;++i) g[i] = f[i-(1<<d)];
for(int i=1;i<2*m;++i) g[i] += g[i-1];
for(int i=2*m-1;i>=(1<<d);--i) g[i] -= g[i-(1<<d)];
for(int i=(2<<d);i<2*m;++i) g[i] += g[i-(2<<d)];
for(int i=2*m-1;i>=m;--i) g[i] -= g[i-m];
for(int i=0;i<2*m;++i) R[i] = g[i];
}
详见 https://www.luogu.com.cn/article/6e3xhab7。
H
较难题,没调出来,呜呜呜。
如果没有修改权值,考虑类似 kruskal 的过程,按权值从大到小加边,出现第一次
考虑维护 100 张图表示边权不小于
I
签到题,按题意模拟即可。
具体实现上,对于当前坐标维护当前字符串
然后随便维护就做完了,也没什么细节。
#include <bits/stdc++.h>
using namespace std;
constexpr int maxn=1e6+9;
struct node
{
int ch[26],nxt,cnt=0,fa=-2,endpos=-1;
}tr[maxn];
int rt=0,ncnt=0;
void insert(char s[],int id)
{
int u=rt;
for(char *p=s;*p;++p)
{
if(!tr[u].ch[(*p)-'a']) tr[u].ch[(*p)-'a']=++ncnt;
tr[tr[u].ch[(*p)-'a']].fa=u;
++tr[u].cnt;
u=tr[u].ch[(*p)-'a'];
}
++tr[u].cnt;
tr[u].endpos=id;
}
int dfs(int u)
{
int sc=0;
for(int i=0;i<26;++i) sc+=!!tr[u].ch[i];
if(!sc) return tr[u].nxt=u;
if(sc>1 || ~tr[u].endpos)
{
for(int i=0;i<26;++i)
if(tr[u].ch[i])
dfs(tr[u].ch[i]);
return tr[u].nxt=u;
}
for(int i=0;i<26;++i)
if(tr[u].ch[i])
return tr[u].nxt=dfs(tr[u].ch[i]);
return 114514;
}
int npos=0,blen=0,n,m;
char s[5000009];
int main()
{
ios::sync_with_stdio(0);
cin.tie(0);cout.tie(0);
cin>>n>>m;
for(int i=1;i<=n;++i)
cin>>s,insert(s,i);
tr[0].nxt=dfs(0);
cin>>s;
for(int i=0;i<m;++i)
{
if(s[i]=='E')
{
if(blen) printf("-1 ");
else printf("%d ",tr[npos].endpos);
npos=blen=0;
continue;
}
if(s[i]=='T')
{
if(!blen) npos=tr[npos].nxt;
continue;
}
if(s[i]=='B')
{
if(blen) --blen;
else if(npos) npos=tr[npos].fa;
continue;
}
if(blen || !tr[npos].ch[s[i]-'a']) ++blen;
else npos=tr[npos].ch[s[i]-'a'];
}
return 0;
}
J
简单题。
对
给出一种考场上想出的构造方式:维护两个集合
接下来证明这么做是对的:考虑对于
接下来我们考虑如何计算最小值。用 bitset 维护当前所有值域中的值是否可以作为一种方案的答案,即
但是值域达到了
记得加滚动数组。
原题解供喷:
容易发现每次就是对 bitset 左移右移或一下然后暴力做不超过 $5000$ 的部分,再加上打乱优化+开小 bitset 即可通过。
具体地,每次 $dp_{i,j+a_i} = dp_{i,j+a_i} \operatorname{or} dp_{i-1,j}$ 直接维护,$dp_{i,|j-a_i|} = dp_{i,|j-a_i|} \operatorname{or} dp_{i-1,j}$ 拆成 $j\ge a_i$(直接 bitset 移位维护)和 $j<a_i$(不超过 $5000$,直接暴力)。
#include <bits/stdc++.h>
using namespace std;
constexpr int maxn=1e4+9;
int a[maxn],n;
bitset<(1<<20)> x[2];
int main()
{
srand(time(0));
scanf("%d",&n);
for(int i=1;i<=n;++i) scanf("%d",a+i);
random_shuffle(a+1,a+n+1);
x[0].set(0);
for(int i=1;i<=n;++i)
{
x[i&1]=(x[~i&1]<<a[i])|(x[~i&1]>>a[i]);
for(int j=a[i];j;--j)
x[i&1][a[i]-j]=x[i&1][a[i]-j]|x[~i&1][j];
}
for(int i=0;i<(1<<20);++i)
if(x[n&1][i]) printf("%d\n",i),exit(0);
return 0;
}
K
中档题。
写出四个转移矩阵作矩阵快速幂即可。
#include<cstdio>
#include<iostream>
#include<cmath>
#include<cstring>
#define ll long long
#define p 998244353
using namespace std;
struct mat{
int a[6][6];
inline mat(){ memset(a,0,sizeof(a)); }
};
inline mat operator * (const mat& lhs,const mat& rhs){
mat res = mat();
for(int i=0;i<6;++i)
for(int j=0;j<6;++j)
for(int k=0;k<6;++k)
res.a[i][j] = (res.a[i][j] + (ll)lhs.a[i][k]*rhs.a[k][j])%p;
return res;
}
inline mat operator + (const mat& lhs,const mat& rhs){
mat res = mat();
for(int i=0;i<6;++i)
for(int j=0;j<6;++j)
res.a[i][j] = (lhs.a[i][j]|rhs.a[i][j])%p;
return res;
}
inline mat power(mat a,ll t){
mat res = mat();
for(int i=0;i<6;++i) res.a[i][i] = 1;
while(t){
if(t&1) res = res*a;
a = a*a;
t >>= 1;
}
return res;
}
ll n;
int k,ans;
int a[5];
mat A,B,C,base;
int main(){
scanf("%lld%d",&n,&k);
for(int i=1;i<=4;++i) scanf("%d",&a[i]);
A.a[0][0] = A.a[0][1] = 1;
for(int i=1;i<5;++i) A.a[i][i+1] = 1;
B.a[4][5] = 1;
for(int i=5;i;--i) B.a[i][i-1] = 1;
C = A+B;
base = mat();
for(int i=0;i<6;++i) base.a[i][i] = 1;
for(int i=1;i<=4;++i){
if(a[i]==1) base = A*base;
else if(a[i]==2) base = B*base;
else base = C*base;
}
base = power(base,n/4);
for(int i=0;i<n%4;++i){
if(a[i+1]==1) base = A*base;
else if(a[i+1]==2) base = B*base;
else base = C*base;
}
for(int i=0;i<6;++i) ans = (ans+base.a[i][5-k])%p;
printf("%d",ans);
return 0;
}
L
中档题。
容易看出光路折射模型,将坐标摊平,二分入射角,维护入射角的
UPD:我们首先处理横坐标变换,改为全部向右走的形式(
细节比较多。
#include <bits/stdc++.h>
using namespace std;
constexpr int maxn=10009;
using ld=long double;
#define eps 1e-12
ld m[maxn],x[maxn];
ld X[maxn];
ld v[maxn];
int main()
{
int n;
ld M,y;
scanf("%d",&n);
scanf("%Lf %Lf",&M,&y);
for(int i=1;i<=n;++i) scanf("%Lf",m+i);
for(int i=1;i<=n;++i) scanf("%Lf",X+i);
int fl=1;
for(int i=1;i<=n;++i){
if(std::abs(X[i])>eps){
fl=0;
}
}
if(fl){
printf("%.9Lf\n",y*M);
return 0;
}
for(int i=1;i<=n;++i)
x[i]=x[i-1]+std::abs(X[i]-X[i-1]);
x[n+1]=x[n]+std::abs(X[n]);
v[++n]=M;
for(int i=n-1;i;--i)
v[i]=v[i+1]+m[i];
ld sum=v[1];
long double l=0,r=std::acos(-1)/2-eps;
long double res=-1,mid,ans=-1;
while(r-l>eps)
{
mid=(l+r)/2; res=0;
long double san=std::sin(mid),cy=0;
int flag=0;
for(int i=1;i<n;++i)
{
ld R=(x[i]-x[i-1])/std::sqrt(1-san*san);
res+=R*v[i];
cy=cy+R*san;
san*=v[i]/v[i+1];
if(san>1){
flag=1;
break;
}
}
ld R=(x[n]-x[n-1])/std::sqrt(1-san*san);
res+=R*v[n];
cy=cy+R*san;
if(flag || cy>y)
{
r=mid;
if(flag!=1)ans=res;
}
else{
l=mid;
ans=res;
}
}
printf("%.9Lf\n",ans);
return 0;
}
M
签到题,按题意模拟即可。需要精细实现,如每轮只访问度数改变的点,当度数恰被减为
考虑证明复杂度,删点操作复杂度为度数之和
#include <bits/stdc++.h>
using namespace std;
struct edge{
int to;
int next;
}e[3333333];
int pe=1111111;
int deg[1111111];
void insert(int a,int to){
e[pe]=(edge){to,e[a].next};
e[a].next=pe++;
deg[to]++;
}
std::vector<int> now,tmp;
int vis[1111111];
void dfs(int o){
vis[o]=1;
for(int p=e[o].next;p;p=e[p].next){
if(!vis[e[p].to]){
dfs(e[p].to);
}
}
}
int main()
{
int n,k;
scanf("%d%d",&n,&k);
for(int i=1;i<n;++i){
int u,v;
scanf("%d%d",&u,&v);
insert(u,v);
insert(v,u);
}
for(int i=1;i<=n;++i){
if(deg[i]==k){
now.push_back(i);
}
}
while(1){
for(int i:now){
vis[i]=1;
for(int p=e[i].next;p;p=e[p].next){
deg[e[p].to]--;
if(deg[e[p].to]==k){
tmp.push_back(e[p].to);
}
}
}
now.clear();
for(int i:tmp){
if(deg[i]==k){
now.push_back(i);
}
}
tmp.clear();
if(now.empty())break;
}
int ans=0;
for(int i=1;i<=n;++i){
if(!vis[i]){
++ans;
dfs(i);
}
}
printf("%d\n",ans);
return 0;
}
N
中档题,考虑贪心。
自底而上每次把找到的满足至少有
考虑证明这个贪心。
假设我们现在有了一个方案,这个方案里面含有一系列的点,表示 dfs 回溯的时候这些点去掉已经选出的子树之后颜色不少于
那么考虑如何调整,向上调整则会影响到它上面一个连通块,导致答案不会更优;向下调整则由于其所有的子树均不满足条件,答案不增。
至于怎么维护,随便线段树合并一下就可以过。
#include <bits/stdc++.h>
struct st{
int cnt;
st* c[2];
void MT(){
cnt=c[0]->cnt+c[1]->cnt;
}
}t[4444444],*nul=t+4444400,*root[222222],*pt=t+1;
st* insert(st* o,int v,int l,int r){
++o->cnt;
if(l==r){
return o;
}
int mid=l+r>>1;
if(v<=mid){
o->c[0]=pt++;
o->c[0]->c[0]=o->c[0]->c[1]=nul;
insert(o->c[0],v,l,mid);
}
else{
o->c[1]=pt++;
o->c[1]->c[0]=o->c[1]->c[1]=nul;
insert(o->c[1],v,mid+1,r);
}
return o;
}
st* merge(st* x,st* y,int l,int r){
if(x==nul)return y;
if(y==nul)return x;
if(l==r){
x->cnt|=y->cnt;
return x;
}
int mid=l+r>>1;
x->c[0]=merge(x->c[0],y->c[0],l,mid);
x->c[1]=merge(x->c[1],y->c[1],mid+1,r);
x->MT();
return x;
}
int col[222222];
struct edge{
int to;
int next;
}e[666666];
int pe=222222;
void insert(int a,int to){
e[pe]=(edge){to,e[a].next};
e[a].next=pe++;
}
int n,k;
int ans=0;
void dfs(int o,int fa){
for(int p=e[o].next;p;p=e[p].next){
if(e[p].to!=fa){
dfs(e[p].to,o);
root[o]=merge(root[o],root[e[p].to],1,n);
}
}
if(root[o]->cnt>=k){
++ans;
root[o]=pt++;
root[o]->c[0]=root[o]->c[1]=nul;
}
}
int main()
{
scanf("%d%d",&n,&k);
for(int i=1;i<=n;++i){
scanf("%d",col+i);
}
for(int i=1;i<n;++i){
int x,y;
scanf("%d %d",&x,&y);
insert(x,y);
insert(y,x);
}
for(int i=1;i<=n;++i){
root[i]=pt++;
root[i]->c[0]=root[i]->c[1]=nul;
root[i]=insert(root[i],col[i],1,n);
}
dfs(1,0);
printf("%d\n",ans);
}
O
对于注意力充沛的选手来说,就是签到题。
注意到
因此答案为
或者有