模拟赛7.7-7.12(week1)
7.7
100+0+0+0=100pts,rk114514。
T1
判断点在多边形内,计算几何板子,可以参考 UVA634。获得 100pts。
Code
#include<bits/stdc++.h>
#define int long long
#define inf 1e18
#define db double
#define gc getchar
#define pc putchar
#define rg register
#define IOS ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
using namespace std;
inline int read(){
int x=0,f=1;
char ch=gc();
while(!isdigit(ch)){
if(ch=='-') f=-f;
ch=gc();
}
while(isdigit(ch)) x=(x<<3)+(x<<1)+ch-'0',ch=gc();
return x*f;
}
inline void write(int x){
if(x<0) pc('-'),x=-x;
if(x>9) write(x/10);
pc(x%10+'0');
}
const db eps=1e-6;
const int N=1145;
inline int sgn(db x){if(fabs(x)<eps) return 0;return x<0?-1:1;}
struct Point{
double x,y;
Point(){}
Point(db x, db y):x(x),y(y){}
Point operator + (Point B){return Point(x+B.x,y+B.y);}
Point operator - (Point B){return Point(x-B.x,y-B.y);}
Point operator * (db k){return Point(x*k,y*k);}
Point operator / (db k){return Point(x/k,y/k);}
bool operator == (Point B){return sgn(x-B.x)==0&&sgn(y-B.y)==0;}
}p[N];
struct Line{
Point p1,p2;
Line(){}
Line(Point p1, Point p2):p1(p1),p2(p2){}
};
typedef Point Vector;
inline bool cmp(Point a, Point b){return atan2(a.y,a.x)<atan2(b.y,b.x);}
db Dot(Vector A, Vector B){return A.x*B.x+A.y*B.y;}
db Cross(Vector A, Vector B){return A.x*B.y-A.y*B.x;}
bool check1(Point P, Line v){return sgn(Cross(P-v.p1,v.p2-v.p1))==0&&sgn(Dot(P-v.p1,P-v.p2))<=0;}
bool check2(Point P){
for(int i=0;i<4;i++) if(P==p[i]) return 1;
for(int i=0;i<4;i++) if(check1(P,Line(p[i],p[(i+1)%4]))) return 1;
int cnt=0;
for(int i=0;i<4;i++){
int c=sgn(Cross(P-p[(i+1)%4],p[i]-p[(i+1)%4]));
int u=sgn(p[i].y-P.y);
int v=sgn(p[(i+1)%4].y-P.y);
if(c>0&&u<0&&v>=0) cnt++;
if(c<0&&u>=0&&v<0) cnt--;
}
return cnt!=0;
}
int n,d,m;
signed main(){
freopen("death.in","r",stdin);
freopen("death.out","w",stdout);
n=read(),d=read(),m=read();
p[0]=Point(0,d);p[1]=Point(d,0);
p[2]=Point(n,n-d);p[3]=Point(n-d,n);
sort(p,p+4,cmp);
while(m--){
db x=read(),y=read();
Point P(x,y);
if(check2(P)) puts("YES");
else puts("NO");
}
return 0;
}
T2
给定一个文本串 \(t\),长度为 \(n\)。但每个字符可能不是唯一确定的,每个位置有 \(k_i\) 种可能的字符。
给 \(q\) 个模式串 \(s_i\),对于每个 \(s_i\),求 \(t\) 中有几个位置时匹配的。
字符串都由小写字母组成。
40pts 可以暴力,但是由于本人太菜,暴力都打不来。
\(k_i=1\) 时有 20pts,此时可以用 ACAM 离线做,但是由于本人太菜,ACAM 板子写挂。
正解
设 \(f_{ch,i}\) 为第 \(i\) 个位置,文本串能否是字符 \(ch\),此时可以把文本串转化为 \(26\) 个二进制串。
考虑在线询问,从高到低遍历模式串每一个字符,去文本串找他对应的二进制串并按位右移 \(i\) (0-index) 位,把所有位置对应的二进制串按位与得到结果 \(res\),若匹配成功会在 \(res\) 的匹配位置得到一个 \(1\),统计 \(1\) 的个数即可。
可以用 bitset 维护二进制串,时间复杂度 \(O(\frac{n^2}{\omega})\)。
Code
#include<bits/stdc++.h>
#define int long long
#define gc getchar
#define pc putchar
#define rg register
#define LB lower_bound
#define UB upper_bound
#define PII pair<int, int>
#define PDI pair<double, int>
#define fi first
#define se second
#define IOS ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
using db=long double;using ll=long long;
using ull=unsigned long long;
using namespace std;
const ll INF=1e18;
const int inf=0x3f3f3f3f;
namespace IO{
inline int read(){
int x=0,f=1;
char ch=gc();
while(!isdigit(ch)){
if(ch=='-') f=-f;
ch=gc();
}
while(isdigit(ch)) x=(x<<3)+(x<<1)+ch-'0',ch=gc();
return x*f;
}
inline void write(int x){
if(x<0) pc('-'),x=-x;
if(x>9) write(x/10);
pc(x%10+'0');
}
}
using namespace IO;
const int N=1e5+10;
bitset<N> f[26],tmp;
char s[N];
int n,m;
signed main(){
freopen("match.in","r",stdin);
freopen("match.out","w",stdout);
n=read();
for(rg int i=1;i<=n;i++){
int len=read();
for(rg int j=1;j<=len;j++){
char ch;cin>>ch;
f[ch-'a'][i]=1;
}
}
m=read();
while(m--){
scanf("%s",s);int len=strlen(s);
tmp=f[s[0]-'a'];
for(rg int i=1;i<len;i++) tmp&=(f[s[i]-'a']>>i);
write(tmp.count());puts("");
}
return 0;
}
T3
lg 原题 P4214
没什么好说的,4.2 做过,感觉挺唐的。模拟赛懒得写了,喜提 0pts。
Code
#include<bits/stdc++.h>
#define gc getchar
#define pc putchar
#define rg register
#define LB lower_bound
#define UB upper_bound
#define PII pair<int, int>
#define PDI pair<double, int>
#define fi first
#define se second
#define IOS ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
using db=double;using ll=long long;
using ull=unsigned long long;
using namespace std;
const ll inf=1e18;
namespace IO{
inline int read(){
int x=0,f=1;
char ch=gc();
while(!isdigit(ch)){
if(ch=='-') f=-f;
ch=gc();
}
while(isdigit(ch)) x=(x<<3)+(x<<1)+ch-'0',ch=gc();
return x*f;
}
inline void write(int x){
if(x<0) pc('-'),x=-x;
if(x>9) write(x/10);
pc(x%10+'0');
}
}
using namespace IO;
const int N=3e3+10;
const int M=9e3+10;
const ull p=131;
int n,m;
namespace SET{
int fa[N];
inline int find(int x){return fa[x]==x?fa[x]:(fa[x]=find(fa[x]));}
inline void merge(int x, int y){int fx=find(x),fy=find(y);if(fx!=fy) fa[fx]=fy;}
}
using namespace SET;
struct edge{
int to,nxt;
}e[M];
int head[N],cnt=1;
inline void addedge(int u, int v){
e[++cnt]={v,head[u]};
head[u]=cnt;
}
ull hsh[N];
int low[N],dfn[N],dcc[N],st[N],tot,top,tim;
bool vis[M];
inline void tarjan(int u, int in){
low[u]=dfn[u]=++tim;
st[++top]=u;
for(rg int i=head[u];i;i=e[i].nxt){
if(vis[i]) continue;
if(i==in||(i^1)==in) continue;
int v=e[i].to;
if(!dfn[v]){
vis[i]=vis[i^1]=1;
tarjan(v,in);
vis[i]=vis[i^1]=0;
low[u]=min(low[u],low[v]);
}
else low[u]=min(low[u],dfn[v]);
}
if(low[u]==dfn[u]){
dcc[u]=++tot;
while(st[top]!=u) dcc[st[top--]]=tot;
top--;
}
}
inline void solve(){
for(rg int i=1;i<=n;i++) hsh[i]=1ull;
for(rg int k=1;k<=m+1;k++){
for(rg int i=1;i<=n;i++) dfn[i]=low[i]=dcc[i]=0;
tot=top=tim=0;
for(rg int i=1;i<=n;i++) if(!dfn[i]) tarjan(i,k<<1);
for(rg int i=1;i<=n;i++) hsh[i]=hsh[i]*p+(ull)dcc[i];
}
}
inline int calc(int u, int v){
if(find(u)!=find(v)) return 0;
if(dcc[u]!=dcc[v]) return 1;
if(hsh[u]==hsh[v]) return 3;
return 2;
}
signed main(){
freopen("flow.in","r",stdin);
freopen("flow.out","w",stdout);
n=read(),m=read();
for(rg int i=1;i<=n;i++) fa[i]=i;
for(rg int i=1;i<=m;i++){
int u=read(),v=read();
addedge(u,v);addedge(v,u);
merge(u,v);
}
solve();
for(rg int i=1;i<=n;i++){
int ans=0;
for(rg int j=1;j<=n;j++)
if(i^j) ans+=calc(i,j);
write(ans);pc(' ');
}
return 0;
}
T4
输出 -1 有 10pts,本人眼睛不好没看到。
总结:我是肺雾吧。
7.8
70+60+0+0=130pts,rk1919
T1
给你一张 \(n\) 个点,\(m\) 条边的无向图,每条边承重上限为 \(w\)。
共 \(q\) 次查询,每次查询从 \(u\) 结点出发的重量为 \(v\) 的车能到达的结点数量。
车只能沿边移动,不能驶过低于自己重量的承重上限的道路。
正解
kruskal 重构树+倍增维护祖先,时间复杂度 \(O(q \log n)\),注意图不一定连通,kruskal 跑出来可能是最大生成森林,本人没注意到因此怒挂 30pts。
Code
#include<bits/stdc++.h>
#define int long long
#define gc getchar
#define pc putchar
#define rg register
#define LB lower_bound
#define UB upper_bound
#define PII pair<int, int>
#define PDI pair<double, int>
#define fi first
#define se second
#define IOS ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
using db=double;using ll=long long;
using ull=unsigned long long;
using namespace std;
const ll INF=1e18;
const int inf=0x3f3f3f3f;
namespace IO{
inline int read(){
int x=0,f=1;
char ch=gc();
while(!isdigit(ch)){
if(ch=='-') f=-f;
ch=gc();
}
while(isdigit(ch)) x=(x<<3)+(x<<1)+ch-'0',ch=gc();
return x*f;
}
inline void write(int x){
if(x<0) pc('-'),x=-x;
if(x>9) write(x/10);
pc(x%10+'0');
}
}
using namespace IO;
const int N=2e5+10;
struct Graph{
int u,v,w;
}edge[N<<1];
struct Edge{
int to,nxt,w;
}e[N<<1];
int n,m,t,fa[N<<1],head[N],cnt;
int val[N<<1],size[N<<1],f[N<<1][21],dep[N<<1];
bool vis[N];
void addedge(int u, int v, int w){e[++cnt]={v,head[u],w};head[u]=cnt;}
bool cmp(Graph a, Graph b){return a.w>b.w;}
inline int find(int x){return (fa[x]==x)?fa[x]:fa[x]=find(fa[x]);}
void init(){for(int i=1;i<=n;i++)fa[i]=i;}
vector<int> g[N];
void kruskal(){
int num=n;
sort(edge+1,edge+1+m,cmp);
for(int i=1;i<=m;i++){
int u=edge[i].u,v=edge[i].v,w=edge[i].w;
int fu=find(u),fv=find(v);
if(fu==fv) continue;
val[++num]=w;fa[fu]=fa[fv]=fa[num]=num;
//addedge(u,v,w);addedge(v,u,w);
g[num].push_back(fu),g[num].push_back(fv);
if(num==2*n-1)break;
}
}
inline void dfs(int u, int father){
dep[u]=dep[father]+1,f[u][0]=father;
for(rg int i=1;(1<<i)<=dep[u];i++) f[u][i]=f[f[u][i-1]][i-1];
for(auto v:g[u]){
dfs(v,u);
size[u]+=size[v];
}
}
signed main(){
freopen("classic.in","r",stdin);
freopen("classic.out","w",stdout);
n=read(),m=read(),t=read();
init();
for(int i=1;i<=m;i++)
{
int u=read(),v=read(),w=read();
edge[i]={u,v,w};
}
kruskal();
for(rg int i=1;i<=n;i++) size[i]=1;
dfs(2*n-1,0);
int lst=0,q=read();
while(q--){
memset(vis,0,sizeof vis);
int u=read(),w=read();
if(t) u^=lst,w^=lst;
// while(val[f[u]]>=w) u=f[u];
for(rg int i=19;i>=0;i--) if(val[f[u][i]]>=w) u=f[u][i];
int ans=size[u];
write(ans);puts("");
lst=ans;
}
return 0;
}
T2
原题 lg P6217
给出一个长度为 \(n\) 的序列 \(a\),\(q\) 次询问 \(\prod\limits_{i=l}^r \operatorname{lcm}(a_i,x)\) 的值,结果对 \(10^9+7\) 取模。
\(O(qn \log V)\) 的暴力居然给了 60pts。
正解
分数线上面可以用快速幂和前缀积。考虑:
考虑整数唯一分解:
注意到值域为 \(2 \times 10^5\),于是指数一定不大,考虑质因数分解,使用一个 vector,存所有 \(p^w\) 在原序列出现过的位置并升序排列,每次查询对 \(x\) 质因数分解,对 \(x\) 的每个 \(p^w\) 在 vector 中 lower_bound 一下,最后用费马小定理求逆元。
时间复杂度 \(O(q \log n \log V)\)
Code
#include<bits/stdc++.h>
#define int long long
#define gc getchar
#define pc putchar
#define rg register
#define LB lower_bound
#define UB upper_bound
#define PII pair<int, int>
#define PDI pair<double, int>
#define fi first
#define se second
#define IOS ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
using db=double;using ll=long long;
using ull=unsigned long long;
using namespace std;
const ll INF=1e18;
const int inf=0x3f3f3f3f;
namespace IO{
inline int read(){
int x=0,f=1;
char ch=gc();
while(!isdigit(ch)){
if(ch=='-') f=-f;
ch=gc();
}
while(isdigit(ch)) x=(x<<3)+(x<<1)+ch-'0',ch=gc();
return x*f;
}
inline void write(int x){
if(x<0) pc('-'),x=-x;
if(x>9) write(x/10);
pc(x%10+'0');
}
}
using namespace IO;
const int N=2e5+10;
const int mod=1e9+7;
int n,q,a[N],prod[N],fac[N];
int prime[N],cnt;
bool vis[N];
vector<int> v[N];
inline int qpow(int a, int b){
int ans=1;
while(b){
if(b&1) ans=ans*a%mod;
a=a*a%mod,b>>=1;
}
return ans;
}
inline void sieve(){
fac[1]=1;
for(rg int i=2;i<N;i++){
if(!vis[i]) prime[++cnt]=i,fac[i]=i;
for(rg int j=1;j<=cnt;j++){
if(i*prime[j]>N) break;
vis[i*prime[j]]=1,fac[i*prime[j]]=prime[j];
if(i%prime[j]==0) break;
}
}
}
signed main(){
freopen("lcm.in","r",stdin);
freopen("lcm.out","w",stdout);
n=read(),q=read();prod[0]=1;
sieve();
for(rg int i=1;i<=n;i++){
a[i]=read(),prod[i]=prod[i-1]*a[i]%mod;
int tmp=a[i];
while(tmp>1){
int k=fac[tmp],p=1;
while(tmp%k==0) tmp/=k,p*=k,v[p].push_back(i);
}
}
while(q--){
int l=read(),r=read(),x=read();
int ans1=qpow(x,r-l+1)%mod,ans2=prod[r]%mod*qpow(prod[l-1],mod-2)%mod;
int res=1;
while(x>1){
int k=fac[x],p=1,tot=0;
while(x%k==0){
x/=k,p*=k;
tot+=UB(v[p].begin(),v[p].end(),r)-LB(v[p].begin(),v[p].end(),l);
}
res=res*qpow(k,tot)%mod;
}
res=qpow(res,mod-2)%mod;
write(ans1%mod*ans2%mod*res%mod);puts("");
}
return 0;
}
T3
lg 原题 P2664。
点分治,本人并不会。
T4
lg 原题 P8392
神秘 dp,本人并不会。
总结:我是肺雾。
7.9
59+60+0+0=119pts,rk1919810
T1
赛时只想出 \(O(n^3)\) 的 dp,喜提 59pts。
正解
考虑 dp,设 \(f_{i,j}\) 为前 \(i\) 位分成 \(k\) 段的方案数。有
考虑对 \(a\) 做前缀和 \(sum\),若区间 \([l,r]\) 为第 \(k\) 段,则有 \(sum_r \equiv sum_{l-1} \pmod k\)。
注意到 \(f_{i,j}\) 只和 \(j-1\) 有关,考虑对前 \(j-1\) 个 \(i\) 每个相同 \(sum_i \bmod j\) 的 \(f_{i,j-1}\) 做前缀和,可以做到 \(O(n^2)\)。
Code
#include<bits/stdc++.h>
#define int long long
#define gc getchar
#define pc putchar
#define rg register
#define LB lower_bound
#define UB upper_bound
#define PII pair<int, int>
#define PDI pair<double, int>
#define fi first
#define se second
#define IOS ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
using db=double;using ll=long long;
using ull=unsigned long long;
using namespace std;
const ll INF=1e18;
const int inf=0x3f3f3f3f;
namespace IO{
inline int read(){
int x=0,f=1;
char ch=gc();
while(!isdigit(ch)){
if(ch=='-') f=-f;
ch=gc();
}
while(isdigit(ch)) x=(x<<3)+(x<<1)+ch-'0',ch=gc();
return x*f;
}
inline void write(int x){
if(x<0) pc('-'),x=-x;
if(x>9) write(x/10);
pc(x%10+'0');
}
}
using namespace IO;
const int N=3e3+10;
const int mod=1e9+7;
int n,a[N],sum[N];
int f[N][N],g[N][N];
signed main(){
freopen("mod.in","r",stdin);
freopen("mod.out","w",stdout);
n=read();
for(rg int i=1;i<=n;i++) a[i]=read(),sum[i]=sum[i-1]+a[i];
g[0][0]=1;
for(rg int j=1;j<=n;j++){
for(rg int i=1;i<=n;i++){
(f[i][j]+=g[sum[i]%j][j-1])%=mod;
(g[sum[i]%j][j-1]+=f[i][j-1])%=mod;
}
}
int ans=0;
for(rg int k=1;k<=n;k++) ans=(ans+f[n][k])%mod;
write(ans);
return 0;
}
T2
原题 lg P11267
细节很多。
赛时对着部分分打,结果被扫码评测机评成 25pts,实际是 60pts。
正解
考虑对每个点维护其所到达的下一个点在一个循环节内的位置与距离,分别设为 \(nxt_i\) 和 \(dis_i\)。
先考虑找第一个点,从 \(1+m\) 开始倒序找 \(1\),找了一轮没找到直接退出,说明 \(nxt_1=2\),记 \(pre=1\),从 \(2\) 开始到 \(n\),继续考察 \(i+m\),不为 \(1\) 则考察 \(pre\)。这样可以算出所有 \(nxt_i\) 和 \(dis_i\)。然后再倍增一下维护即可,单次查询时 \(O(\log k)\) 的,总时间复杂度为 \(O(n+n \log k +q \log k)\)。
Code
#include<bits/stdc++.h>
#define int long long
#define gc getchar
#define pc putchar
#define rg register
#define LB lower_bound
#define UB upper_bound
#define PII pair<int, int>
#define PDI pair<double, int>
#define fi first
#define se second
#define IOS ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
using db=double;using ll=long long;
using ull=unsigned long long;
using namespace std;
const ll INF=1e18;
const int inf=0x3f3f3f3f;
namespace IO{
inline int read(){
int x=0,f=1;
char ch=gc();
while(!isdigit(ch)){
if(ch=='-') f=-f;
ch=gc();
}
while(isdigit(ch)) x=(x<<3)+(x<<1)+ch-'0',ch=gc();
return x*f;
}
inline void write(int x){
if(x<0) pc('-'),x=-x;
if(x>9) write(x/10);
pc(x%10+'0');
}
}
using namespace IO;
const int N=2e5+10;
const int mod=1e9+7;
string s;
int n,m,q;
int nxt[N][65],dis[N][65];
signed main(){
freopen("travel.in","r",stdin);
freopen("travel.out","w",stdout);
n=read(),m=read(),q=read();
cin>>s;int pre=0;
for(rg int i=0;i<n;i++){
int pos=i+m;
if(!i){
int cnt=0;
while(s[pos%n]!='1'&&pos!=i){
pos--,cnt++;
if(cnt>=n){pos=i;break;}
}
}else {if(s[pos%n]!='1') pos=pre;}
if(pos==i) pos=i+1;
pre=pos,nxt[i][0]=pos%n,dis[i][0]=(pos-i)%mod;
}
for(rg int i=1;i<=62;i++){
for(rg int j=0;j<n;j++){
nxt[j][i]=nxt[nxt[j][i-1]][i-1];
dis[j][i]=(dis[j][i-1]+dis[nxt[j][i-1]][i-1])%mod;
}
}
while(q--){
int s=read(),k=read();
int res=s%mod;
s--,s%=n;
for(rg int i=62;i>=0;i--){
if((k>>i)&1){
(res+=dis[s][i])%=mod;
s=nxt[s][i];
}
}
write(res);puts("");
}
return 0;
}
T3
原题 lg P2076
40pts 是可以用 \(O(n^2 \log n\)) 的 kruskal 做的,当然 \(O(n^2)\) 的 Prim 更优,维护生成树上 LCA 即可,可以参考 P1967,赛时数组开小导致 RE,警钟敲烂。
据说 Boruvka 双 log 有 60pts,但本人太菜根本不会,听说有人平面最近点对人类智慧过了 %%%。
膜拜 Rigel 用人类智慧的 kruskal 卡到 60pts%%%%%%%%%%%%%%%%%%。
40pts
#include<bits/stdc++.h>
//#define int long long
#define inf 0x3f3f3f3f
#define gc getchar
#define pc putchar
#define lowbit(x) (x)&(-x)
using namespace std;
inline int read()
{
int x=0,f=1;
char ch=gc();
while(!isdigit(ch))
{
if(ch=='-')
f=-f;
ch=gc();
}
while(isdigit(ch))
x=(x<<3)+(x<<1)+ch-'0',ch=gc();
return x*f;
}
inline void write(int x)
{
if(x<0)
pc('-'),x=-x;
if(x>9)
write(x/10);
pc(x%10+'0');
return;
}
const int N=2e5+10;
const int M=3e7+10;
struct Graph{
int u,v,w;
}edge[M];
struct Edge{
int to,nxt,w;
}e[N<<1];
int h,w,n,q,m,fa[N],head[N],cnt;
int f[N][20],dep[N],dis[N][20];
bool vis[N];
struct node{
int x,y;
}a[N];
void addedge(int u, int v, int w){
e[++cnt]={v,head[u],w};
head[u]=cnt;
}
bool cmp(Graph a, Graph b){return a.w<b.w;}
inline int find(int x){return (fa[x]==x)?fa[x]:fa[x]=find(fa[x]);}
void init(){for(int i=1;i<=n;i++)fa[i]=i;}
void kruskal()
{
int num=0;
sort(edge+1,edge+1+m,cmp);
for(int i=1;i<=m;i++)
{
int u=edge[i].u,v=edge[i].v,w=edge[i].w;
int fu=find(u),fv=find(v);
if(fu==fv)
continue;
fa[fu]=fv;
addedge(u,v,w);
addedge(v,u,w);
if(++num==n)
break;
}
}
void dfs(int u, int father, int weight)
{
vis[u]=1;
dep[u]=dep[father]+1;
f[u][0]=father;
dis[u][0]=weight;
for(int i=1;(1<<i)<=dep[u];i++)
{
f[u][i]=f[f[u][i-1]][i-1];
dis[u][i]=max(dis[u][i-1],dis[f[u][i-1]][i-1]);
}
for(int i=head[u];i;i=e[i].nxt)
{
int v=e[i].to,w=e[i].w;
if(v!=father)
dfs(v,u,w);
}
}
inline int LCA(int x, int y)
{
if(find(x)!=find(y))
return -1;
int ans=-inf;
if(dep[x]<dep[y])
swap(x,y);
for(int i=19;i>=0;i--)
if(dep[x]-(1<<i)>=dep[y])
{
ans=max(ans,dis[x][i]);
x=f[x][i];
}
if(x==y)
return ans;
for(int i=19;i>=0;i--)
if(f[x][i]!=f[y][i])
{
ans=max(ans,max(dis[x][i],dis[y][i]));
x=f[x][i],y=f[y][i];
}
ans=max(ans,max(dis[x][0],dis[y][0]));
return ans;
}
signed main()
{
freopen("bottle.in","r",stdin);
freopen("bottle.out","w",stdout);
h=read(),w=read(),n=read(),q=read();
init();
for(int i=1;i<=n;i++) a[i].x=read(),a[i].y=read();
for(int i=1;i<=n;i++)
for(int j=1;j<=i;j++)
if(i^j) edge[++m]={i,j,abs(a[i].x-a[j].x)+abs(a[i].y-a[j].y)};
kruskal();
for(int i=1;i<=n;i++) if(!vis[i]) dfs(i,0,0);
while(q--)
{
int x=read(),y=read();
write(LCA(x,y));
puts("");
}
return 0;
}
正解本人并不会。
T4
lg P6803
太难了本人并不会。
7.10
100+0+0+0=100pts,rk1919
怎么这几天老是挂?
今天起晚了,7:50 出寝,早饭没吃 8:00 直接开打比赛。
T1
有 \(n\) 个点。从 \(0\) 到 \(n-1\) 标号。在其中加入了 \(m\) 组边,第 \(i\) 组边可以被表示为 \((x,w)\),表示对于每个点 \(i\) 均和 \((i+x)\bmod n\) 连接了一条权值为 \(w\) 的边。求这张图最小生成树的权值,不连通输出 \(-1\)。
正解
考察每组边,对于每个 \(x\),\((i+x) \bmod n\) 会构成 \(\gcd(x,n)\) 个环,环长为 \(\frac{n}{\gcd(x,n)}\)。
考虑模拟 kruskal 加边的过程,把所有边组按权值升序排,设 \(cnt\) 为连通块个数,对于每个新的环,加边后 \(cnt \leftarrow \gcd(cnt,x)\),增加的权值为 \((cnt-\gcd(cnt,x))w\),最终 \(cnt=1\) 时退出,\(cnt \neq 1\) 直接不连通。
Code
#include<bits/stdc++.h>
#define int long long
#define gc getchar
#define pc putchar
#define rg register
#define LB lower_bound
#define UB upper_bound
#define PII pair<int, int>
#define PDI pair<double, int>
#define fi first
#define se second
#define IOS ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
using db=double;using ll=long long;
using ull=unsigned long long;
using namespace std;
const ll INF=1e18;
const int inf=0x3f3f3f3f;
namespace IO{
inline int read(){
int x=0,f=1;
char ch=gc();
while(!isdigit(ch)){
if(ch=='-') f=-f;
ch=gc();
}
while(isdigit(ch)) x=(x<<3)+(x<<1)+ch-'0',ch=gc();
return x*f;
}
inline void write(int x){
if(x<0) pc('-'),x=-x;
if(x>9) write(x/10);
pc(x%10+'0');
}
}
using namespace IO;
const int N=3e5+10;
int n,m;
PII e[N];
signed main(){
freopen("mst.in","r",stdin);
freopen("mst.out","w",stdout);
n=read(),m=read();
for(rg int i=1;i<=m;i++){
int x=read(),w=read();
e[i]={w,x};
}
sort(e+1,e+1+m);
int cnt=n,ans=0;
for(rg int i=1;i<=m;i++){
if(e[i].se==n) continue;
int tmp=cnt;
cnt=__gcd(cnt,e[i].se);
ans+=(tmp/cnt-1)*cnt*e[i].fi;
if(cnt==1) return write(ans),0;
}
write(-1);
return 0;
}
T2
给出了一个 \(N\) 个点 \(M\) 条边的有向图。求有多少个有序点对 \((a,b)\),满足至少存在一个点 \(c\) 以及从 \(c\) 到 \(a\) 的一条路径 \(L_a\),\(c\) 到 \(b\) 的一条路径 \(L_b\),使得 \(L_a\) 的长度是 \(L_b\) 的两倍(长度指的经过的边的数目)(\(|L_a|,|L_b|\ge 0\))。注意不一定是简单路径。
赛时想了半天 tarjan,结果是错误的,其实是本人太菜了。
好像全机房没几个 AC 的,甚至部分分都难写,基本都保龄。
正解
考虑 dp,设 \(f_{i,j}=1/0\) 为点对 \((i,j)\) 是否符合条件,初始 \(f_{i,i}=1\),对于每个 \(i\) 可以沿边搜索,先对 \(i\) 走两步,再对 \(j\) 走一步,得到的两个点记录答案。
但我们还需记录状态,可以给 f 再加一维 \(f_{i,j,0/1/2}\) 表示状态对 \(j\) 走一步或初始状态、对 \(i\) 走一步、对 \(i\) 走两步,具体的状态转移如下:
写个 bfs 扩展即可,时间复杂度为 \(O(nm)\)。
Code
#include<bits/stdc++.h>
#define int long long
#define gc getchar
#define pc putchar
#define rg register
#define LB lower_bound
#define UB upper_bound
#define PII pair<int, int>
#define PDI pair<double, int>
#define fi first
#define se second
#define IOS ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
using db=double;using ll=long long;
using ull=unsigned long long;
using namespace std;
const ll INF=1e18;
const int inf=0x3f3f3f3f;
namespace IO{
inline int read(){
int x=0,f=1;
char ch=gc();
while(!isdigit(ch)){
if(ch=='-') f=-f;
ch=gc();
}
while(isdigit(ch)) x=(x<<3)+(x<<1)+ch-'0',ch=gc();
return x*f;
}
inline void write(int x){
if(x<0) pc('-'),x=-x;
if(x>9) write(x/10);
pc(x%10+'0');
}
}
using namespace IO;
const int N=3e3+10;
int n,m,dp[N][N][3];
vector<int> g[N];
struct node{int a,b,stp;};
queue<node> q;
signed main(){
freopen("dierti.in","r",stdin);
freopen("dierti.out","w",stdout);
n=read(),m=read();
for(rg int i=1;i<=m;i++){
int u=read(),v=read();
g[u].push_back(v);
}
for(rg int i=1;i<=n;i++){
dp[i][i][0]=1;
q.push({i,i,0});
}
while(!q.empty()){
node f=q.front();q.pop();
int step=f.stp,u=f.a,v=f.b;
if(step==0){
for(int u1:g[u]){
if(!dp[u1][v][1]){
dp[u1][v][1]=1;
q.push({u1,v,1});
}
}
}else if(step==1){
for(int u2:g[u]){
if(!dp[u2][v][2]){
dp[u2][v][2]=1;
q.push({u2,v,2});
}
}
}else{
for(int v1:g[v]){
if(!dp[u][v1][0]){
dp[u][v1][0]=1;
q.push({u,v1,0});
}
}
}
}
int ans=0;
for(rg int i=1;i<=n;i++) for(rg int j=1;j<=n;j++) ans+=dp[i][j][0];
write(ans);
return 0;
}
T3
给定一张 \(n\) 个点 \(2n\) 条边的有向图,点标号为 \(0\sim n-1\),其中 \(n\) 是正偶数。
对于所有的 \(x\in[0,n-1]\),满足存在 \(x\to 2x\bmod n,x\to (2x-1)\bmod n\) 的有向边。
求图上是否存在哈密顿回路,如果有请你给出一条。
进一步的,你需要对于所有 \(\leq n\) 的正偶数 \(m\) 均给出回答。
如果输出存在性但乱构造方案可以获得 20pts,也可以通过爆搜加打表获得很高的分数,本人提交的时候不小心把 freopen 注释掉了(唐飞了)。据说好像是 JS 某年省选 D1T1?
正解
此做法由机房一位巨佬提出。
考察连边的性质,可以考虑反向连边倒着找,由于 \(0\) 有自环,考虑以 \(0\) 为起点,\(\frac{n}{2}\) 为终点,单次询问时间复杂度 \(O(n)\)。
Code
#include<bits/stdc++.h>
#define int long long
#define gc getchar
#define pc putchar
#define rg register
#define LB lower_bound
#define UB upper_bound
#define PII pair<int, int>
#define PDI pair<double, int>
#define fi first
#define se second
#define IOS ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
using db=double;using ll=long long;
using ull=unsigned long long;
using namespace std;
const ll INF=1e18;
const int inf=0x3f3f3f3f;
namespace IO{
inline int read(){
int x=0,f=1;
char ch=gc();
while(!isdigit(ch)){
if(ch=='-') f=-f;
ch=gc();
}
while(isdigit(ch)) x=(x<<3)+(x<<1)+ch-'0',ch=gc();
return x*f;
}
inline void write(int x){
if(x<0) pc('-'),x=-x;
if(x>9) write(x/10);
pc(x%10+'0');
}
}
using namespace IO;
const int N=5e2+10;
bool vis[N];
stack<int> st;
inline int pre(int x){return (x+1)>>1;}
inline void solve(int n){
puts("YES");
int cnt=n,now=n>>1;
fill(vis,vis+n,0);
while(cnt){
vis[now]=1,cnt--;
st.push(now);
if(vis[pre(now)%n]) now=pre(now+n)%n;
else now=pre(now)%n;
}
while(!st.empty()){
write(st.top()),pc(' ');
st.pop();
}
puts("");
}
signed main(){
freopen("manhattan.in","r",stdin);
freopen("manhattan.out","w",stdout);
int n=read();
for(rg int i=2;i<=n;i+=2) solve(i);
return 0;
}
T4
原题 lg P6846
正解容斥+状压,本人并不会。
总结:怎么越来越菜了?
7.11
100+25+0+0=125pts,rk31
T1 20min 写完,T2 想了 3h 想出了假做法,可能是我不会笛卡尔树的原因吧,T3T4没去写。
T1
有一个圆形泳池,泳池中随机分布着 \(n\) 只鸭子,求问存在一条直径使得所有鸭子均在直径的一侧的概率,答案对 \(998244353\) 取模。
答案其实是 \(\frac{n}{2^{n-1}}\),非常不好证,感性理解好像是这样的。
T2
给定一个长度为 \(n\) 的非负整数序列 \(a\)。
定义,
选择 \(\{1,2,3,...,n\}\) 的一个子集 \(S\),使得 \(|S|\ge K\) ,定义 \(C_K=\sum\limits_{i\in S}\sum\limits_{j\in S} f(i,j)\)
对于每个 \(1\le K\le n\),求 \(C_K\) 的最小值。
很阴间,赛时想了很久的假做法,主要转移式推不来(本人过于菜),而且是笛卡尔树上的 dp,最后写了 20pts 的部分分,其他点乱搞居然反向挂 5pts 也是很逆天了。
正解
考虑对原序列建笛卡尔树。
考虑 dp,设 \(f_{u,i}\) 为以 \(u\) 为根的子树,选了 \(i\) 个节点的最小值。
考虑转移,考虑根与子树合并,左子树不选根节点:
选根节点:
右子树同理。
考虑左右子树与根节点的合并,枚举左右子树节点数 \(i,j\),不选根节点:
选根节点:
转移看似是 \(O(n^3)\),实则是 \(O(n^2)\) 的,因为合并两个子树时,两个循环上界都只循环到子树大小,每一对点都只在 LCA 处合并一次。
Code
#include<bits/stdc++.h>
#define int long long
#define gc getchar
#define pc putchar
#define rg register
#define LB lower_bound
#define UB upper_bound
#define PII pair<int, int>
#define PDI pair<double, int>
#define fi first
#define se second
#define IOS ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
using db=double;using ll=long long;
using ull=unsigned long long;
using namespace std;
const ll INF=1e18;
const int inf=0x3f3f3f3f;
namespace IO{
inline int read(){
int x=0,f=1;
char ch=gc();
while(!isdigit(ch)){
if(ch=='-') f=-f;
ch=gc();
}
while(isdigit(ch)) x=(x<<3)+(x<<1)+ch-'0',ch=gc();
return x*f;
}
inline void write(int x){
if(x<0) pc('-'),x=-x;
if(x>9) write(x/10);
pc(x%10+'0');
}
}
using namespace IO;
const int N=5e3+10;
int n,a[N],ls[N],rs[N],in[N],size[N];
int stk[N],top,rt,f[N][N];
inline void dfs(int u){
f[u][0]=0,f[u][1]=a[u],size[u]=1;
if(!ls[u]&&!rs[u]) return;
if(ls[u]) dfs(ls[u]),size[u]+=size[ls[u]];
if(rs[u]) dfs(rs[u]),size[u]+=size[rs[u]];
if(!rs[u]){
for(rg int i=1;i<=size[ls[u]];i++){
f[u][i]=min(f[u][i],f[ls[u]][i]);
f[u][i+1]=min(f[u][i+1],f[ls[u]][i]+(i+1)*a[u]);
}
return;
}
if(!ls[u]){
for(rg int i=1;i<=size[rs[u]];i++){
f[u][i]=min(f[u][i],f[rs[u]][i]);
f[u][i+1]=min(f[u][i+1],f[rs[u]][i]+(i+1)*a[u]);
}
return;
}
for(rg int i=0;i<=size[ls[u]];i++)
for(rg int j=0;j<=size[rs[u]];j++){
f[u][i+j]=min(f[u][i+j],f[ls[u]][i]+f[rs[u]][j]+i*j*a[u]);
f[u][i+j+1]=min(f[u][i+j+1],f[ls[u]][i]+f[rs[u]][j]+(i+1)*(j+1)*a[u]);
}
}
signed main(){
freopen("future.in","r",stdin);
freopen("future.out","w",stdout);
n=read();
for(rg int i=1;i<=n;i++) a[i]=read();
top=1;
for(rg int i=1;i<=n;i++){
int k=top;
while(k>0&&a[stk[k]]<a[i]) k--;
if(k) rs[stk[k]]=i;
if(k<top) ls[i]=stk[k+1];
stk[++k]=i;top=k;
}
memset(f,0x3f,sizeof f);
for(rg int i=1;i<=n;i++) in[ls[i]]++,in[rs[i]]++;
for(rg int i=1;i<=n;i++) if(!in[i]) rt=i;
dfs(rt);
for(rg int i=1;i<=n;i++) write(f[rt][i]),puts("");
return 0;
}
T3
你是 NFLSPC 众多在线法官之一,与众多愚蠢的在线法官不同的是,你的人(机?)设是“慈爱的在线法官”。除此之外,你还是凯文同学的家长。
现在凯文同学正在参加 NFLSPC。NFLSPC 总共有 \(n\) 道题,第 \(i\) 道题目的总分是 \(a_i\),而凯文同学在第 \(i\) 道题目的得分是 \(b_i\)。
作为在线法官之一,你拥有进入后台的权限。后台中有 \(m\) 个按钮,第 \(i\) 个按钮可以用一个长度为 \(n\) 的 01 串 \(s_i\) 表示。当按下第 \(i\) 个按钮的时候,对于每个 \(j\in[1,n]\),若 \(s_{i,j}\) 为 0,则凯文同学的第 \(j\) 题分数不变,否则假设凯文同学第 \(j\) 题之前得分为 \(b_j\),按了按钮后会变成分数会变成 \(a_j-b_j\)。但是执行操作需要时间,按一次第 \(i\) 个按钮会占用 \(t_i\) 单位时间。
你可以按下任意多的按钮,每个按钮也可以按下多次。作为慈爱的在线法官与凯文的家长,你自然是希望凯文最后的得分最小。在得分最小的前提下,你还希望最小化占用的时间。
FXT 出的某次 NFLSPC 的不知道 T 几,赛时跟 T2 死磕导致没怎么看(其实是本人太菜)。
正解要状压,而且似乎跑的很慢。FXT 提供了一种逆天做法,按时间升序排然后暴力考察前 100~200 个就能直接 AC,因为数据随机,据说 50 组只有 1 组需要考虑 100 个以外,200 个直接造不出来。
当时 NFLSPC 只过了 6 个。
T4
集训队互测 2025 D1T1
本人并不会。
总结:dp 需要多练,然后不要总是想着冲正解,可以先考虑部分分然后再看其他题,分数并不一定总是整百的。
7.12
20+0+10+0=30pts,rk30
去你妈的整百,30pts 是人打的出来的吗,这个成绩还 NOIP,退役算了吧。
死磕 T1,T2 没去想做法,T3 01-trie 板题 ZJ 讲过但是我没去写(唐飞了),T4 神秘期望题。
FXT 下播了,今天组卷的是 cmh。
T1
\(n \times m\) 的网格,共 \((n+1) \times (m+1)\) 个格点,求选 \(k\) 个格点使得全在同一条直线上的方案数。
赛时脑子短路,连平移线段都没想到,然后写了个部分分。
正解
考虑枚举线段两个端点,但是这样是 \(O(n^4)\) 的,考虑固定一端为原点,然后枚举终点,线段可以平移,负斜率直接镜像一下即可。
Code
#include<bits/stdc++.h>
#define int long long
#define gc getchar
#define pc putchar
#define rg register
#define LB lower_bound
#define UB upper_bound
#define PII pair<int, int>
#define PDI pair<double, int>
#define fi first
#define se second
#define IOS ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
using db=double;using ll=long long;
using ull=unsigned long long;
using namespace std;
const ll INF=1e18;
const int inf=0x3f3f3f3f;
namespace IO{
inline int read(){
int x=0,f=1;
char ch=gc();
while(!isdigit(ch)){
if(ch=='-') f=-f;
ch=gc();
}
while(isdigit(ch)) x=(x<<3)+(x<<1)+ch-'0',ch=gc();
return x*f;
}
inline void write(int x){
if(x<0) pc('-'),x=-x;
if(x>9) write(x/10);
pc(x%10+'0');
}
}
using namespace IO;
const int N=5e3+10;
const int mod=1e9+7;
int n,m,k,c[N][N];
signed main(){
freopen("kgrid.in","r",stdin);
freopen("kgrid.out","w",stdout);
n=read(),m=read(),k=read();
if(k==0) return write(0),0;
if(k==0) return write((n+1)*(m+1)%mod),0;
c[0][0]=1;
for(rg int i=1;i<N;i++){
c[i][0]=c[i][i]=1;
for(rg int j=1;j<i;j++) c[i][j]=(c[i-1][j]+c[i-1][j-1])%mod;
}
int ans=0;
for(rg int i=0;i<=n;i++)
for(rg int j=0;j<=m;j++){
int d=__gcd(i,j)+1;
if(d>=k){
int res=c[d-2][k-2]%mod*(m-j+1)%mod*(n-i+1)%mod;
if(i&&j) res=res*2%mod;
ans=(ans+res)%mod;
}
}
write(ans%mod);
return 0;
}
T2
原题 lg P8997
一堆 min,max,赛时想到建树,但并不知道做法,因为在死磕 T1,cmh 给的神秘结论居然是猜的。
正解
考虑把 min,max 等建一棵二叉树,可以考虑分治然后合并子树。
神秘结论:答案的集合是一段连续的区间。
严格证明我不会,但是感性理解一下,答案只与子树内数的相对大小有关,所以可以考虑交换数使得两棵子树内相对大小不变。
考虑对每棵子树维护一个三元组 \((l,r,len)\) 表示子树内所选区间的左端点、右端点以及子树大小。考虑子树合并,若为 \(\min\),则 \((l_1,r_1,len_1),(l_2,r_2,len_2) \rightarrow (\min(l_1,l_2),r_1+r_2-1,len_1+len_2)\);若为 \(\max\),则 \((l_1,r_1,len_1),(l_2,r_2,len_2) \rightarrow (l_1+l_2,\max(r_1+len_2,r_2+len_1),len_1+len_2)\)。
Code
#include<bits/stdc++.h>
#define int long long
#define gc getchar
#define pc putchar
#define rg register
#define LB lower_bound
#define UB upper_bound
#define PII pair<int, int>
#define PDI pair<double, int>
#define fi first
#define se second
#define IOS ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
using db=double;using ll=long long;
using ull=unsigned long long;
using namespace std;
const ll INF=1e18;
const int inf=0x3f3f3f3f;
namespace IO{
inline int read(){
int x=0,f=1;
char ch=gc();
while(!isdigit(ch)){
if(ch=='-') f=-f;
ch=gc();
}
while(isdigit(ch)) x=(x<<3)+(x<<1)+ch-'0',ch=gc();
return x*f;
}
inline void write(int x){
if(x<0) pc('-'),x=-x;
if(x>9) write(x/10);
pc(x%10+'0');
}
}
using namespace IO;
struct node{int l,r,len;};
string s;
int n,pos;
inline node solve(){
if(s[pos++]=='?') return {1,1,1};
char ch=s[pos];
pos+=3;
node ls=solve();pos++;
node rs=solve();pos++;
if(ch=='i') return {min(ls.l,rs.l),ls.r+rs.r-1,ls.len+rs.len};
else return {ls.l+rs.l,max(ls.r+rs.len,rs.r+ls.len),ls.len+rs.len};
}
signed main(){
freopen("magic.in","r",stdin);
freopen("magic.out","w",stdout);
IOS
cin>>s;
node ans=solve();
cout<<ans.r-ans.l+1;
return 0;
}
T3
原题 hdu7526
维护 01-trie,本人暂时并不会。
T4
原题 CF1392H。
神秘期望题,本人并不会。
没有总结。

浙公网安备 33010602011771号