【LGR-060】洛谷10月月赛 I div.1&div.2
Preface
一边打一边写作文打的像shit,T2失智严重特判错了233
Orz Div1 Rank2的foreverlastnig聚聚,顺便说一句显然Luogu的比赛质量比以往显著提高了啊
以下题目按难度顺序排序
P5587 打字练习
送分模拟题,注意行首退格的问题以及一个坑点:范文中也有退格
#include<cstdio>
#include<iostream>
#include<string>
#define RI register int
#define CI const int&
using namespace std;
const int N=10005;
string a[N],b[N],tp; int ca,cb,cur,pre[N*10],t,lim; bool vis[N*10];
inline void get_str(string& s)
{
s=""; char ch; while ((ch=getchar())!='\n') s+=ch;
}
inline void del(string& s)
{
RI i; string t=s; s=""; lim=t.size();
for (i=0;i<lim;++i) pre[i]=i-1,vis[i]=1;
for (i=0;i<lim;++i) if (t[i]=='<')
{
vis[i]=0; pre[i+1]=pre[i];
if (~pre[i]) vis[pre[i]]=0,pre[i+1]=pre[pre[i]];
}
for (i=0;i<lim;++i) if (vis[i]) s+=t[i];
}
int main()
{
//freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout);
while (get_str(tp),tp!="EOF") a[++ca]=tp;
while (get_str(tp),tp!="EOF") b[++cb]=tp;
RI i,j; for (i=1;i<=ca;++i) del(a[i]);
for (i=1;i<=cb;++i) del(b[i]);
for (i=1;i<=min(ca,cb);++i)
for (j=0,lim=min(a[i].size(),b[i].size());j<lim;++j)
cur+=a[i][j]==b[i][j]; scanf("%d",&t);
return printf("%d",(int)(1.0*cur/(1.0*t/60)+0.5)),0;
}
P5588 小猪佩奇爬树
分类讨论题。考虑我们对于每种颜色,如果有三个及以上的点的子树内是没有这种颜色的点的,那么显然这种颜色的答案就是\(0\)
否则若有两个的话就是两端点的子树\(size\)乘积
剩下的就是一些特殊的情况,比如所有点在从上而下的一条链上的,那么答案就是下面的点的\(size\)乘上\(顶上的点的n-\text{顶上的点的size}\)
还有只有一个点的以及没有点的都要单独讨论
#include<cstdio>
#include<cctype>
#define int long long
#define RI register int
#define CI const int&
#define Tp template <typename T>
using namespace std;
const int N=3000005;
struct edge
{
int to,nxt;
}e[N<<1]; int n,head[N],cnt,col[N],x,y,pnum[N],all[N],fir[N],size[N],plc[N],f[N],g[N],ct[N];
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++)
#define pc(ch) (Ftop!=Fend?*Ftop++=ch:(fwrite(Fout,1,S,stdout),*(Ftop=Fout)++=ch))
char Fin[S],Fout[S],*A,*B,*Ftop,*Fend; int pt[25];
public:
FileInputOutput(void) { Ftop=Fout; Fend=Fout+S; }
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()));
}
Tp inline void write(T x)
{
if (x<0) pc('-'),x=-x; RI ptop=0; while (pt[++ptop]=x%10,x/=10);
while (ptop) pc(pt[ptop--]+48); pc('\n');
}
inline void flush(void)
{
fwrite(Fout,1,Ftop-Fout,stdout);
}
#undef tc
#undef pc
}F;
inline void addedge(CI x,CI y)
{
e[++cnt]=(edge){y,head[x]}; head[x]=cnt;
e[++cnt]=(edge){x,head[y]}; head[y]=cnt;
}
#define to e[i].to
inline void DFS(CI now=1,CI fa=0)
{
int tp=++ct[col[now]],fir=0,flag=0; size[now]=1;
for (RI i=head[now];i;i=e[i].nxt) if (to!=fa)
{
int ltp=ct[col[now]]; DFS(to,now); f[col[now]]+=size[now]*size[to];
size[now]+=size[to]; if (ltp!=ct[col[now]]) ++flag,fir=to;
}
f[col[now]]+=size[now]*(n-size[now]);
if (flag+(tp!=1||ct[col[now]]!=all[col[now]])==1)
{
++pnum[col[now]];
if (pnum[col[now]]==1) g[col[now]]=size[now]; else
if (pnum[col[now]]==2) g[col[now]]*=fir?(n-size[fir]):size[now];
}
}
#undef to
signed main()
{
//freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout);
RI i; for (F.read(n),i=1;i<=n;++i) F.read(col[i]),++all[col[i]];
for (i=1;i<n;++i) F.read(x),F.read(y),addedge(x,y);
for (DFS(),i=1;i<=n;++i)
{
if (!ct[i]) F.write(1LL*n*(n-1)/2LL); else if (ct[i]==1) F.write(f[i]);
else if (pnum[i]==2) F.write(g[i]); else F.write(0);
}
return F.flush(),0;
}
P5589 小猪佩奇玩游戏
首先记\(d(x)\)表示有多少个数在删除时会把\(x\)删掉,利用概率的可加性我们发现一个数的贡献就是\(\frac{1}{d(x)}\)
那么撇开暴力计算的过程,我们发现很多数的\(d(x)=1\),那么这就意味着我们只要找出所有的\(d(x)\not=1\)的数计算贡献即可
考虑直接暴力枚举\(i\),然后把\(d(i^k)+1\),用map
存储答案即可
最后跑出来发现\(n\)以内的数目很少,因此直接爆枚就可以了
PS:这题有容斥的\(\log^2 n\)的做法,我太菜了所以不会
#include<cstdio>
#include<map>
#define RI register int
#define CI const int&
using namespace std;
typedef map <int,int>:: iterator MI;
const int N=1e9;
int t,n,num; double ans; map <int,int> ct;
int main()
{
RI i; long long cur; for (i=2;i*i<=N;++i)
for (cur=i*i;cur<=N;cur*=i) ++ct[cur];
for (scanf("%d",&t);t;--t)
{
scanf("%d",&n); num=n; ans=0;
for (MI it=ct.begin();it!=ct.end();++it)
if (it->first<=n) --num,ans+=1.0/(it->second+1); else break;
printf("%.6lf\n",ans+num);
}
return 0;
}
P5590 赛车游戏
(以下偷懒直接贴了在Luogu写的题解并稍作修改)
白天比赛的时候基本上都写出来了,结果判无用边的时候脑抽了一下挂了
然而我还以为是后面写跪了一直在魔调233
首先看到这题我们先日常考虑一些简单的情况,我们来分析一下:
若起点不可达终点则输出\(-1\)(题面里之前没加上,可TM坑死人了)
若存在一个点无法从起点到达或者是无法到达终点(比赛的时候脑抽写成且了233)那么这个点就是无用的,可以把它删掉,那么所有与它相连的边都可以随便赋值
然后考虑将剩下的边再建成图,那么此时出现环的话环上一定不合法,因此也可以判掉
那么我们惊喜地发现现在剩下的图已经是个DAG了,并且从起点到每个点的所有路径长度都要相同,那我们按拓扑序逐步转移即可。接下来有两种做法:
第一种是我比赛的时候写的,比较诡异,正确性感觉是对的但是又证明不来
考虑先拓扑排序一遍,求出将边长视为\(1\)时从起点到每个点的路径长度区间范围\([l_i,r_i]\)
那么我们考虑化边权为点权,把每个点到它的路径总长作为点权\(val_i\),那么显然每个\(val_i\)的取值范围就是\([r_i,9\times l_i]\)
考虑\(1\)号点的点权可以定下为\(0\),那么对于接下来的每个点,如果它的前驱点\(j\)的点权为\(val_j\),那么它的取值区间应该对\([val_j+1,val_j+9]\)取交
那么我们得出每个点的取值区间后直接在里面随便取一个值即可(顺手取最小值),同时再判掉一些无解的情况
乍一看随便取可能会错,但是这里的后面的点权范围是在前面的路径情况下考虑过的结果,因此可以通过此题
#include<cstdio>
#include<vector>
#include<cstring>
#define RI register int
#define CI const int&
using namespace std;
typedef vector <int>:: iterator VI;
const int N=2005,INF=1e9;
struct interval
{
int l,r;
inline interval(CI L=-INF,CI R=INF)
{
l=L; r=R;
}
friend inline interval operator & (const interval& A,const interval& B)
{
return interval(max(A.l,B.l),min(A.r,B.r));
}
inline void operator &=(const interval& ots)
{
*this=*this&ots;
}
}v[N]; int n,m,x[N],y[N],z[N],q[N],val[N];
bool f1[N],f2[N]; vector <int> pre[N];
struct Graph
{
struct edge
{
int to,nxt;
}e[N]; int head[N],cnt,deg[N];
inline void clear(void)
{
memset(head,0,n+1<<2); memset(deg,0,n+1<<2); cnt=0;
}
inline void addedge(CI x,CI y)
{
e[++cnt]=(edge){y,head[x]}; head[x]=cnt; ++deg[y];
}
#define to e[i].to
inline void BFS(CI st,bool *vis)
{
RI H=0,T=1; vis[q[1]=st]=1; while (H<T)
{
int now=q[++H]; for (RI i=head[now];i;i=e[i].nxt)
if (!vis[to]) vis[to]=1,q[++T]=to;
}
}
inline bool Top_Sort(void)
{
RI H=0,T=0,i; for (i=1;i<=n;++i) if (!deg[i]) q[++T]=i;
while (H<T)
{
int now=q[++H]; for (i=head[now];i;i=e[i].nxt)
if (pre[to].push_back(now),!--deg[to]) q[++T]=to;
}
return T==n;
}
#undef to
}A,B;
int main()
{
//freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout);
RI i,j; for (scanf("%d%d",&n,&m),i=1;i<=m;++i)
scanf("%d%d",&x[i],&y[i]),A.addedge(x[i],y[i]),B.addedge(y[i],x[i]);
for (A.BFS(1,f1),B.BFS(n,f2),i=1;i<=m;++i) if (!f1[x[i]]||!f2[x[i]]) z[i]=1;
if (!f1[n]) return puts("-1"),0;
for (A.clear(),i=1;i<=m;++i) if (!z[i]) A.addedge(x[i],y[i]);
if (!A.Top_Sort()) return puts("-1"),0; for (v[1]=interval(0,0),i=2;i<=n;++i)
{
int mi=INF,mx=-INF; for (VI it=pre[q[i]].begin();it!=pre[q[i]].end();++it)
mi=min(mi,v[*it].l+1),mx=max(mx,v[*it].r+1); v[q[i]]=interval(mi,mx);
}
for (i=1;i<=n;++i) if (v[i]=interval(v[i].r,9*v[i].l),v[i].l>v[i].r)
return puts("-1"),0; for (i=2;i<=n;++i)
{
interval tp; for (VI it=pre[q[i]].begin();it!=pre[q[i]].end();++it)
tp&=interval(val[*it]+1,val[*it]+9); v[q[i]]&=tp;
if (v[q[i]].l>v[q[i]].r) return puts("-1"),0; val[q[i]]=v[q[i]].l;
}
for (i=1;i<=m;++i) if (!z[i]) z[i]=val[y[i]]-val[x[i]];
for (printf("%d %d\n",n,m),i=1;i<=m;++i) printf("%d %d %d\n",x[i],y[i],z[i]);
return 0;
}
当然还有另一种更简单正确性也有保证的做法,我们考虑直接顺推每个点的权值区间,那么此时这个点的取法就会影响到后面了,因此我们可以倒着再做一遍,这样就可以保证正确性
#include<cstdio>
#include<vector>
#include<cstring>
#define RI register int
#define CI const int&
using namespace std;
typedef vector <int>:: iterator VI;
const int N=2005,INF=1e9;
struct interval
{
int l,r;
inline interval(CI L=-INF,CI R=INF)
{
l=L; r=R;
}
friend inline interval operator & (const interval& A,const interval& B)
{
return interval(max(A.l,B.l),min(A.r,B.r));
}
inline void operator &=(const interval& ots)
{
*this=*this&ots;
}
}v[N]; int n,m,x[N],y[N],z[N],q[N],val[N];
bool f1[N],f2[N]; vector <int> pre[N];
struct Graph
{
struct edge
{
int to,nxt;
}e[N]; int head[N],cnt,deg[N];
inline void clear(void)
{
memset(head,0,n+1<<2); memset(deg,0,n+1<<2); cnt=0;
}
inline void addedge(CI x,CI y)
{
e[++cnt]=(edge){y,head[x]}; head[x]=cnt; ++deg[y];
}
#define to e[i].to
inline void BFS(CI st,bool *vis)
{
RI H=0,T=1; vis[q[1]=st]=1; while (H<T)
{
int now=q[++H]; for (RI i=head[now];i;i=e[i].nxt)
if (!vis[to]) vis[to]=1,q[++T]=to;
}
}
inline bool Top_Sort(void)
{
RI H=0,T=0,i; for (i=1;i<=n;++i) if (!deg[i]) q[++T]=i;
while (H<T)
{
int now=q[++H]; for (i=head[now];i;i=e[i].nxt)
if (pre[to].push_back(now),!--deg[to]) q[++T]=to;
}
return T==n;
}
#undef to
}A,B;
int main()
{
//freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout);
RI i,j; for (scanf("%d%d",&n,&m),i=1;i<=m;++i)
scanf("%d%d",&x[i],&y[i]),A.addedge(x[i],y[i]),B.addedge(y[i],x[i]);
for (A.BFS(1,f1),B.BFS(n,f2),i=1;i<=m;++i) if (!f1[x[i]]||!f2[x[i]]) z[i]=1;
if (!f1[n]) return puts("-1"),0;
for (A.clear(),i=1;i<=m;++i) if (!z[i]) A.addedge(x[i],y[i]);
if (!A.Top_Sort()) return puts("-1"),0; v[1]=interval(0,0);
for (i=2;i<=n;++i) for (VI it=pre[q[i]].begin();it!=pre[q[i]].end();++it)
v[q[i]]&=interval(v[*it].l+1,v[*it].r+9);
for (i=n;i>1;--i) for (VI it=pre[q[i]].begin();it!=pre[q[i]].end();++it)
v[*it]&=interval(v[q[i]].l-9,v[q[i]].r-1);
for (i=1;i<=n;++i) if (v[i].l>v[i].r) return puts("-1"),0;
for (i=1;i<=m;++i) if (!z[i]) z[i]=v[y[i]].l-v[x[i]].l;
for (printf("%d %d\n",n,m),i=1;i<=m;++i) printf("%d %d %d\n",x[i],y[i],z[i]);
return 0;
}
PS1:LTL的做法(即第二种做法)被叉掉了233
PS2:这题正解好像是差分约束,我想了想还挺有道理
P5591 小猪佩奇学数学
因为被没带草稿本那粉笔在机房写了两黑板233
来吧让我们来推式子QAQ,首先第一步考虑把下取整拆了:
考虑对于括号内的式子前后分别计算,首先是
有组合数有幂次考虑怎么化成二项式定理的形式,我们考虑用组合数吸收掉\(i\):
好了上面是热身,然后考虑怎么搞后面那部分:
\(i\mod k\)显然很麻烦,我们考虑枚举\(d=i\mod k\),那么有\((i-d)\mod k=0\),代进去有:
然后下一步就要用到单位根反演了,不会的可以看浅谈单位根反演,代进去有:
发现后面那个\(\sum_{d=0}^{k-1} d\times(w_k^{k-j})^d\)是做这题的关键,考虑怎么快速计算一个一般形式的问题:
方法其实挺多的,这里我用的是扰动法(不知道的可以到 《具体数学》 上看下):
前面那一项我们发现\(S\)又被我们凑出来了,而后面那一项可以用等比数列求和得到通式,则:
移项就得到\(S=\frac{n\times r^n}{r-1}-\frac{r^{n+1}-r}{(r-1)^2} (r\not=1)\)
然后想必大家也发现了这里没有考虑\(r=1\)的情况,而显然有:
综上所述这题就被解决了,而且由于\(998244352=2^{23}\times 119\),因此在\(k\in\{2^{\omega}|0\le\omega\le 20\}\)时是存在单位根的
#include<cstdio>
#define RI register int
#define CI const int&
using namespace std;
const int N=1<<20|5,mod=998244353;
int n,p,k,g,w[N],ans,ret;
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 inc(int& x,CI y)
{
if ((x+=y)>=mod) x-=mod;
}
inline void dec(int& x,CI y)
{
if ((x-=y)<0) x+=mod;
}
inline int sub(CI x,CI y)
{
int t=x-y; return t<0?t+mod:t;
}
inline int S(CI r,CI n)
{
if (r==1) return 1LL*n*(n-1)%mod*quick_pow(2)%mod;
return sub(1LL*n*quick_pow(r,n)%mod*quick_pow(r-1)%mod,1LL*sub(quick_pow(r,n+1),r)*quick_pow(r-1,mod-3)%mod);
}
int main()
{
scanf("%d%d%d",&n,&p,&k); g=quick_pow(3,(mod-1)/k);
RI i; for (w[0]=i=1;i<=k;++i) w[i]=1LL*w[i-1]*g%mod;
for (ans=1LL*n*p%mod*quick_pow(p+1,n-1)%mod,i=0;i<k;++i)
inc(ret,1LL*quick_pow((1LL*w[i]*p%mod+1)%mod,n)*S(w[k-i],k)%mod);
dec(ans,1LL*quick_pow(k)*ret%mod);
return printf("%d",1LL*quick_pow(k)*ans%mod),0;
}
P5592 美德的讲坛
我太菜了,以下解法出自官方题解
首先考虑找出一个\(\omega\),满足\(2^{\omega}\le x<2^{\omega+1}\),然后我们把\(a_i\)按照\(\lfloor \frac{a_i}{2^{\omega}}\rfloor\)分组
那么首先我们发现每个组内部的两两的异或都是\(<x\)的
然后细细分析我们发现只有相邻的组之前才有可能产生\(<x\)的异或值,因此我们把这两组的点拿出来,问题变成:
现在有两组点,左边每个点有一个权值 \(a_i\),右边每个点有一个权值\(b_i\)。现在要在左右各选出一些点,使得两两异或和\(<x\)
考虑用最小割来解决,如果我们将源点向左边的点连流量\(1\)的边,右边的点向汇点连流量\(1\)的边,然后当\(a_i\operatorname{xor} b_j\ge x\)时,\(i\)向\(j\)连流量\(\infty\)的边
那么跑出这个图的最小割,我们发现这个割就是要删去一些数字与相应源汇点的边,然后使得剩下的数字两两异或都\(<x\)的方案
然后考虑从最小割等于最大流的角度入手,由于这里是匹配问题我们用模拟费用流的思想,同时由于涉及异或我们建立0/1Trie
我们发现这个图的最大流也就是保留 \(a_i\operatorname{xor} b_j\ge x\)的边时,该二分图的最大匹配。
考虑在Trie树上统计,令\(solve(a,b,dep)\)表示当Trie树上以\(x,y\)为根的子树之间进行匹配,两棵子树的最大深度均为\(dep\)
然后记\(a_0,a_1,b_0,b_1\)表示\(a/b\)的左/右子树,用\(|a|\)表示\(a\)的子树里的点数
我们分类讨论,当\(x\)在\(dep\)这一位为\(1\)时,就意味着只有\(a_0\)和\(b_1\),\(a_1\)和\(b_0\)可以匹配
此时答案就是\(solve(a_0,b_1,dep-1)+solve(a_1,b_0,dep-1)\)
当\(x\)在\(dep\)这一位为\(0\)时,那么\(a_0\)和\(b_1\),\(a_1\)和\(b_0\)一定可以匹配
- \(|a_0|<|b_1|\)且\(|a_1|<|b_0|\)时,答案就是\(|a|\)
- \(|a_0|>|b_1|\)且\(|a_1|>|b_0|\)时,答案就是\(|b|\)
- \(|a_0|<|b_1|\)且\(|a_1|>|b_0|\)时,答案就是\(\min(solve(a_1,b_1,dep-1),|b_1|-|a_0|,|a_1|-|b_0|)+|a_0|+|b_0|\)
- \(|a_0|>|b_1|\)且\(|a_1|<|b_0|\)时,答案就是\(\min(solve(a_0,b_0,dep-1),|b_0|-|a_1|,|a_0|-|b_1|)+|a_1|+|b_1|\)
然后注意讨论下\(a=b\)的情况以及\(x=0\)的情况即可
最后修改由于每次最多只有\(\log\)的节点被改动了,因此类似于记忆化搜索来解决
总复杂度\((n+q)\log^2 \max(a_i)\),足以通过此题
#include<cstdio>
#include<iostream>
#define RI register int
#define CI const int&
#define CL const LL&
using namespace std;
typedef long long LL;
const int N=100005,R=60;
int n,q,x,rt; LL a[N],s,y;
class Zero_One_Trie
{
private:
struct segment
{
int ch[2],size,ans;
}node[N*R<<2]; bool vis[N*R<<2]; int tot;
public:
inline Zero_One_Trie(void) { rt=tot=1; }
#define lc(x) node[x].ch[0]
#define rc(x) node[x].ch[1]
#define S(x) node[x].size
#define A(x) node[x].ans
inline void insert(int& now,CL val,CI dep=R-1)
{
if (!~dep) return; if (!now) now=++tot; vis[now]=0; ++S(now);
insert(node[now].ch[(val>>dep)&1LL],val,dep-1);
}
inline void remove(int& now,CL val,CI dep=R-1)
{
if (!~dep) return; vis[now]=0; --S(now);
remove(node[now].ch[(val>>dep)&1LL],val,dep-1);
}
inline int solve(CI x=rt,CI y=rt,CI dep=R-1)
{
if (!x||!y||!S(x)||!S(y)) return 0; if (!~dep) return x==y?(S(x)-(S(x)&1)):min(S(x),S(y));
if (vis[x]&&vis[y]) return A(x); vis[x]=vis[y]=1; int ret;
if ((s>>dep)&1LL)
{
if (x==y) ret=solve(lc(x),rc(x),dep-1);
else ret=solve(lc(x),rc(y),dep-1)+solve(rc(x),lc(y),dep-1);
} else
{
int ax=solve(lc(x),lc(y),dep-1),ay=solve(rc(x),rc(y),dep-1);
if (x==y) ret=min(S(lc(x))-ax,S(rc(x))-ay)+ax+ay; else
{
if ((S(lc(x))<=S(rc(y)))==(S(rc(x))<=S(lc(y)))) ret=min(S(x),S(y)); else
if (S(lc(x))<S(rc(y))) ret=min(ay,min(S(rc(y))-S(lc(x)),S(rc(x))-S(lc(y))))+S(lc(x))+S(lc(y));
else ret=min(ax,min(S(lc(y))-S(rc(x)),S(lc(x))-S(rc(y))))+S(rc(x))+S(rc(y));
}
}
return A(x)=A(y)=ret;
}
#undef lc
#undef rc
#undef S
#undef A
}Trie;
int main()
{
RI i; for (scanf("%d%d%lld",&n,&q,&s),i=1;i<=n;++i)
scanf("%lld",&a[i]),Trie.insert(rt,a[i]);
if (!s) { for (RI i=1;i<=q+1;++i) puts("1"); return 0; }
for (printf("%d\n",n-Trie.solve()),i=1;i<=q;++i)
scanf("%d%lld",&x,&y),Trie.remove(rt,a[x]),
Trie.insert(rt,a[x]=y),printf("%d\n",n-Trie.solve());
return 0;
}
Postscript
我发现现在我对一切都一无所知