可达 2025 暑期集训笔记:CSP-S 沃斯班
Day 1
字典树
const int _mxn=1e6+5;
struct trie
{
int tr[_mxn][26],idx,fl[_mxn],cnt[_mxn];
void insert(string s)
{
int nw=0;
for(int i=0;i<s.size();i++)
{
int t=s[i]-'a';
if(!tr[nw][t])
tr[nw][t]=++idx;
nw=tr[nw][t];
cnt[nw]++;
}
fl[nw]++;
}
int query(string s)
{
int nw=0;
for(int i=0;i<s.size();i++)
{
int t=s[i]-'a';
if(!tr[nw][t])
return -1;
nw=tr[nw][t];
}
return fl[nw];
}
}tr;
Day 1 模拟赛
A
题意
现有 \(n\) 个人前来排队买票,其中第 \(0\) 人站在队伍最前方 ,第 \((n - 1)\) 人站在队伍最后方。给定每个人要买的票数,每个人买一张票都需要用掉 恰好 \(1\) 秒,之后到队尾重新排队,如果买完就离队。求第 \(k\) 人完成买票需要的时间/秒。
题解
直接用队列模拟,每个元素用 pair
存编号和剩余要买票数。
代码:
#define ll long long
const int _mxn=1000+5;
int n,k,a[_mxn];
int main()
{
___();
cin>>n>>k;
queue<pair<int,int> > q;
for(int i=0;i<n;i++)//注意是 0~(n-1)
cin>>a[i],q.push(make_pair(i,a[i]));
for(int i=1;;i++)
{
int id=q.front().first,t=q.front().second-1;
q.pop();
if(t==0)
{
if(id==k)//k 买完了
{
cout<<i<<endl;//输出时间
break;//结束
}
}
else//还有要买的,重新入队
q.push(make_pair(id,t));
}
return 0;
}
B
题意
给定一个数组,求出其中第 \(k\) 大的数字减去第 \(k\) 小的数字的值 \(m\),并判断 \(m\) 是否为质数。
题解
大水题。直接排个序把 \(m\) 求出来,然后标准试除法判质数即可。
代码:
#define ll long long
const int _mxn=10000+5;
int n,k,a[_mxn];
bool ispri(int x)
{
if(x<2)
return false;
for(int i=2;i*i<=x;i++)
if(x%i==0)
return false;
return true;
}
int main()
{
___();
cin>>n>>k;
for(int i=1;i<=n;i++)
cin>>a[i];
sort(a+1,a+n+1);
int ans=a[n-k+1]-a[k];
cout<<(ispri(ans)?"YES":"NO")<<endl;
cout<<ans<<endl;
return 0;
}
C
题意
给定一个数组 \(b\),请你找到一对数,满足 \(b_i\oplus b_j\ge k\) 且使得 \(j−i+1\) 的值尽可能小,输出最小值。如果找不到这样一对数,则输出 \(-1\)。
题解
考虑用 01 Trie 维护异或值。在插入时处理子树对应 \(i\) 值的最大值,查询时同时把当前数和 \(k\) 的对应二进制位取出来,分情况讨论。具体看代码:
#define ll long long
const int _mxn=2e5+5;
int tr[_mxn*32][2],cnt=0,mxi[_mxn*32];
int n,k,b[_mxn];
void insert(int x,int id)
{
int nw=0;
for(int i=30;i>=0;i--)
{
mxi[nw]=max(mxi[nw],id);//求出最大 i
int t=(x>>i)&1;
if(!tr[nw][t])
tr[nw][t]=++cnt;
nw=tr[nw][t];
}
mxi[nw]=max(mxi[nw],id);
}
int query(int x,int id)
{
int nw=0,res=0;
for(int i=30;i>=0;i--)
{
int tx=(x>>i)&1,tk=(k>>i)&1;
if(tk)//k 这一位为 1
{
if(tr[nw][!tx])//异或不一样为 1,所以找不同的
nw=tr[nw][!tx];
else//没有就无解,返回一个极大值
return _mxn;
}
else//为 0
{
if(tr[nw][!tx])//有不同的,后面怎么取都比 k 大,直接求答案返回
return id-mxi[tr[nw][!tx]]+1;
else//直接往下遍历
nw=tr[nw][tx];
}
}
return id-mxi[nw]+1;
}
int main()
{
___();
cin>>n>>k;
if(k==0)//特判,保险
{
cout<<1<<endl;
return 0;
}
int ans=_mxn;
for(int i=1;i<=n;i++)
{
cin>>b[i];
insert(b[i],i);
ans=min(ans,query(b[i],i));//取最小值
}
cout<<(ans>n?-1:ans)<<endl;
return 0;
}
D
题意
题解
取一个根节点,预处理每个点到根节点的异或和。不难发现,\(\operatorname{xor}(u,v)=\operatorname{xor}(u,\operatorname{root})\oplus\operatorname{xor}(v,\operatorname{root})\),所以问题就被转换成了一个数组取两个数求最大异或值(洛谷 P10471)。用 01 Trie 维护,贪心选不同的,可以最大。
代码:
#define ll long long
const int _mxn=1e5+5;
int tr[_mxn*32][2],cnt=0;
void insert(int x)
{
int nw=0;
for(int i=31;i>=0;i--)
{
int t=(x>>i)&1;
if(!tr[nw][t])
tr[nw][t]=++cnt;
nw=tr[nw][t];
}
}
ll query(ll x)
{
int nw=0;
ll res=0;
for(int i=31;i>=0;i--)
{
int t=(x>>i)&1;
if(tr[nw][!t])//找不同的
{
res+=1<<i;//加上
nw=tr[nw][!t];
}
else//没有不同的
nw=tr[nw][t];
}
return res;
}
int n,a[_mxn];
typedef int w_type;
struct node
{
int v;
w_type w;
node(){}
node(int _v,w_type _w):v(_v),w(_w){}
bool operator<(node x) const {return w<x.w;}
bool operator>(node x) const {return w>x.w;}
};
vector<node> g[_mxn];
void add(int u,int v,w_type w){g[u].push_back(node(v,w));}
void dfs(int u,int fa)//预处理每个点到根节点的异或和
{
for(auto it:g[u])
{
if(it.v==fa)
continue;
a[it.v]=(a[u]^it.w);
dfs(it.v,u);
}
}
int main()
{
___();
cin>>n;
for(int i=2;i<=n;i++)
{
int u,v,w;
cin>>u>>v>>w;
add(u,v,w),add(v,u,w);
}
a[1]=0;//根节点取个 1
dfs(1,-1);
for(int i=1;i<=n;i++)//预处理出来的值插入字典树
insert(a[i]);
ll ans=0;
for(int i=1;i<=n;i++)//求最大值
ans=max(ans,query(a[i]));
cout<<ans<<endl;
return 0;
}
E
题意
洛谷 P8306(这个模拟赛样例都没改,一堆 fusu
())
题解
字典树板子。
#define ll long long
const int _mxn=3e6+5;
int tr[_mxn][64],cnt=0,fl[_mxn];
int chtoint(char c)//字符转数字
{
if(isdigit(c))
return c-'0';
if(isupper(c))
return c-'A'+1+'9'-'0';
else
return c-'a'+1+'Z'-'A'+1+'9'-'0';
}
void insert(string s)
{
int nw=0;
for(int i=0;i<s.size();i++)
{
int t=chtoint(s[i]);
if(!tr[nw][t])
tr[nw][t]=++cnt;
nw=tr[nw][t];
fl[nw]++;//统计经过这里的字符串个数
}
}
int query(string s)
{
int nw=0;
for(int i=0;i<s.size();i++)
{
int t=chtoint(s[i]);
if(!tr[nw][t])
return 0;
nw=tr[nw][t];
}
return fl[nw];//输出个数
}
int main()
{
___();
int _;
cin>>_;
while(_--)
{
for(int i=0;i<=cnt;i++)//多测清零
for(int j=0;j<62;j++)
tr[i][j]=0;
for(int i=0;i<=cnt;i++)
fl[i]=0;
cnt=0;
int n,q;
cin>>n>>q;
for(int i=1;i<=n;i++)
{
string s;
cin>>s;
insert(s);
}
while(q--)
{
string t;
cin>>t;
cout<<query(t)<<endl;
}
}
return 0;
}
F
题意
在魔法研究院中,需要设计一个符文词典系统,支持以下操作:
符文刻录:将新的符文序列记录到词典中
符文占卜:检查是否存在与占卜模式匹配的符文序列(占卜符 .
可匹配任意单个符文)
题解
字典树板子,但是查询要动点手脚。直接看代码吧:(话说这么暴力似乎还是正解)
#define ll long long
const int _mxn=1e4+5;
int tr[_mxn*25][26],cnt;
bool fl[_mxn*25];
void insert(string s)
{
int nw=0;
for(int i=0;i<s.size();i++)
{
int t=s[i]-'a';
if(!tr[nw][t])
tr[nw][t]=++cnt;
nw=tr[nw][t];
}
fl[nw]=true;
}
bool query(string s,int nw,int st)
{
for(int i=st;i<s.size();i++)
{
int t=s[i]-'a';
if(s[i]=='.')
{
for(s[i]='a';s[i]<='z';s[i]++)//是 . 就枚举所有字母改一下然后递归查
if(tr[nw][s[i]-'a']&&query(s,nw,i))
return true;
return false;
}
else if(!tr[nw][t])
return false;
nw=tr[nw][t];
}
return fl[nw];
}
int main()
{
___();
string op,s;
while(cin>>op>>s)
{
if(op=="add")
insert(s);
else
cout<<(query(s,0,0)?"true":"false")<<endl;
}
return 0;
}
Day 2
单调栈、单调队列
Day 2 模拟赛
A
题意
题解
我直接暴力 dfs 全排列。记得化简给的比例,不然就是 95 分 WA 到底错哪了()(话说不化简到洛谷能过)
代码:
#define ll long long
const int _mxn=+5;
int a,b,c;
int t[15],vis[15];
bool f=false;
void dfs(int dep)
{
if(dep>9)
{
int x=0,y=0,z=0;
for(int i=1;i<=3;i++)
x=x*10+t[i];
for(int i=4;i<=6;i++)
y=y*10+t[i];
for(int i=7;i<=9;i++)
z=z*10+t[i];
if(x%a==0&&y%b==0&&z%c==0&&x/a==y/b&&x/a==z/c)//判断一下
f=true,cout<<x<<" "<<y<<" "<<z<<endl;
return;
}
for(int i=1;i<=9;i++)
{
if(!vis[i])
{
vis[i]=true;
t[dep]=i;
dfs(dep+1);
vis[i]=false;
}
}
}
int main()
{
___();
cin>>a>>b>>c;
int g=__gcd(__gcd(a,b),c);
a/=g,b/=g,c/=g;//除以三个数的 gcd
if(a==0||b==0||c==0)//特判
{
cout<<"No!!!"<<endl;
return 0;
}
dfs(1);//全排列
if(!f)
cout<<"No!!!"<<endl;
return 0;
}
B
题意
给定一个 \(n\times n\) 的 01 矩阵,可以把最多一个 \(0\) 变成 \(1\),求操作后能获得的最大四连通块大小。
题解
跑个 dfs 给每个连通块染个色并统计大小,然后枚举每个 \(0\),找到和这个格子相邻的连通块并累加大小,最后取最大值就行了。
代码:
#define ll long long
const int _mxn=500+5;
int dx[]={0,1,0,-1},dy[]={1,0,-1,0};
int n,a[_mxn][_mxn];
int vis[_mxn][_mxn];//染色数组
int ans=0;
void dfs(int x,int y,int s)
{
vis[x][y]=s;
for(int i=0;i<4;i++)
{
int tx=x+dx[i],ty=y+dy[i];
if(tx>=1&&tx<=n&&ty>=1&&ty<=n&&!vis[tx][ty]&&a[tx][ty])
dfs(tx,ty,s);
}
}
int cnt[_mxn*_mxn],s=1;//cnt[k] 为 k 色的连通块大小
bool flag[_mxn*_mxn];
void solve()//预处理染色
{
memset(vis,0,sizeof(vis));
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
if(a[i][j]&&!vis[i][j])
dfs(i,j,s),s++;
memset(cnt,0,sizeof(cnt));
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
cnt[vis[i][j]]++;//计数
for(int i=1;i<=s;i++)//取一下最大值
ans=max(ans,cnt[i]);
}
int main()
{
___();
cin>>n;
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
cin>>a[i][j];
solve();
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
{
if(!a[i][j])//为零
{
memset(flag,false,sizeof(flag));
int res=1;//这一个格子也要加上
for(int d=0;d<4;d++)//四个方向查找
{
int tx=i+dx[d],ty=j+dy[d];
if(tx>=1&&tx<=n&&ty>=1&&ty<=n&&a[tx][ty])
{
if(!flag[vis[tx][ty]])
{
res+=cnt[vis[tx][ty]];
flag[vis[tx][ty]]=true;//标一下防止重复加
}
}
}
ans=max(ans,res);
}
}
cout<<ans<<endl;
return 0;
}
C
题意
给定一个 \(n\times m\) 的由 F
和 R
组成的字符矩阵,求最大的 F
正方形的面积。
题解
去年做过类似的(Day 2 B),只不过是长方形。同样的方法,求答案时长宽取较小值平方就行了。
采用悬线法,设 \(h_{i,j}\)表示以 \((i,j)\) 为下端点的悬线的最长长度,\(l_{i,j},r_{i,j}\) 分别为悬线有该长度时能向左和向右的距离。答案即为 \(\max(\min(h_{i,j},(r_{i,j}−l_{i,j}+1))^2)\)。
代码:
#define ll long long
const int _mxn=5000+5;
int n,m;
bool a[_mxn][_mxn];
int h[_mxn][_mxn],l[_mxn][_mxn],r[_mxn][_mxn];//
int main()
{
___();
cin>>n>>m;
for(int i=1;i<=n;i++)
{
for(int j=1;j<=m;j++)
{
char c;
cin>>c;
a[i][j]=(c=='F'?1:0);//改一下存储省点空间,不然 MLE
h[i][j]=1,l[i][j]=r[i][j]=j;
}
for(int j=2;j<=m;j++)//当悬线长 1 时处理 l[i][j] 和 r[i][j]
if(a[i][j]&&a[i][j-1])
l[i][j]=l[i][j-1];
for(int j=m-1;j>=1;j--)
if(a[i][j]&&a[i][j+1])
r[i][j]=r[i][j+1];
}
int ans=0;
for(int i=1;i<=n;i++)//处理 h[i][j] 和对应 l[i][j] 和 r[i][j]
{
for(int j=1;j<=m;j++)
{
if(a[i][j])
{
if(a[i-1][j])
{
h[i][j]=h[i-1][j]+1;
l[i][j]=max(l[i][j],l[i-1][j]);
r[i][j]=min(r[i][j],r[i-1][j]);
}
ans=max(ans,min((r[i][j]-l[i][j]+1),h[i][j])*min((r[i][j]-l[i][j]+1),h[i][j]));//求答案
}
}
}
cout<<ans<<endl;
return 0;
}
D
题意
给定一个 \(a\times b\) 的非负整数矩阵,求每个 \(n\times n\) 的区域中极差的最小值。
题解
单调队列预处理每一行长度为 \(n\) 的区间中的极值,然后枚举左上角,往下 \(n\) 行找到总极值,最后求答案,时间复杂度 \(O(abn)\)。
代码:
#define ll long long
const int _mxn=1000+5;
int a,b,n,t[_mxn][_mxn],mx[_mxn][_mxn],mn[_mxn][_mxn];
int main()
{
___();
cin>>a>>b>>n;
for(int i=1;i<=a;i++)//读入 & 预处理
{
deque<int> qn,qx;
for(int j=1;j<=b;j++)
{
cin>>t[i][j];
while(!qn.empty()&&t[i][qn.back()]>=t[i][j])
qn.pop_back();
while(!qn.empty()&&j-qn.front()>=n)
qn.pop_front();
qn.push_back(j);
while(!qx.empty()&&t[i][qx.back()]<=t[i][j])
qx.pop_back();
while(!qx.empty()&&j-qx.front()>=n)
qx.pop_front();
qx.push_back(j);
if(j>=n)
{
mn[i][j]=t[i][qn.front()];//第 i 行 [j-n+1,j] 最小值
mx[i][j]=t[i][qx.front()];//第 i 行 [j-n+1,j] 最大值
}
}
}
int ans=1e9;
for(int i=1;i+n-1<=a;i++)//枚举左上角
{
for(int j=n;j<=b;j++)
{
int mnt=1e9,mxt=0;
for(int k=i;k<i+n;k++)//往下 n 行
{
mnt=min(mnt,mn[k][j]);//区域内最小值
mxt=max(mxt,mx[k][j]);//区域内最大值
}
ans=min(ans,mxt-mnt);
}
}
cout<<ans<<endl;
return 0;
}
E
题意
对于任一长度为 \(n\) 的数组 \(a\),定义 \(\operatorname{cost}(a)=\sum_{i=1}^{n}\operatorname{mex}\{[a_1,a_2,\cdots,a_i]\}\),其中 \(\operatorname{mex}\) 指最小的集合中未出现的非负整数。
给定一个集合 \(\{0,1,2,\cdots,n−1\}\) 的排列 \(p\),求所有循环移位后的排列 \(p'\) 中,\(\operatorname{cost}(p')\)的最大值。其中循环移位指将数组任意长度(可以为 \(0\))的前缀移到最后。
题解
老师的 std:
#include<bits/stdc++.h>
using namespace std;
#define MAX 250005
int n,a[MAX];
bool vis[MAX];
int mex[MAX];
#define ll long long
ll ans,cost;
int st[MAX],head,tail;
void sol(){
cin>>n;
for(int i=0;i<=n+1;i++) vis[i]=0;
int nw=0;
head=1,tail=0;
cost=0;
for(int i=1;i<=n;i++){
cin>>a[i];vis[a[i]]=1;
while(vis[nw]) nw++;
mex[i]=nw;cost+=mex[i];
//记录初始状态的前缀mex和cost值
while(tail&&mex[st[tail]]==mex[i]) tail--;
//前缀mex满足单调不降,可以用单调容器来储存分界点
st[++tail]=i;
}
ans=cost;
for(int i=1;i<n;i++){ //不断的把第一个数移动到最后
cost-=mex[st[head]];//先删除掉第一个数的贡献
if(st[head]==i) head++; //如果队头被删除了,那么要出队
int c=a[i];
// st[head-1]=i;
while(head<=tail&&mex[st[tail]]>c){ //那些mex大于c的前缀,mex都会被修改成c
cost-=1ll*mex[st[tail]]*(st[tail]-st[tail-1]);
//先减去这些位置对cost的贡献
tail--;
}
st[++tail]=n+i-1; mex[st[tail]]=c;
//产生了新的分界点,该点的mex值是c
cost+=1ll*c*(st[tail]-st[tail-1]);
//把这一段mex值为c的贡献全部加上
mex[n+i]=n; st[++tail]=n+i; cost+=n;
//最后一个位置的mex值为n,单独考虑
ans=max(ans,cost);
// for(int j=i+1;j<=n+i;j++)
// cout<<mex[j]<<" ";
// cout<<endl;
}
cout<<ans<<"\n";
}
int main(){
ios::sync_with_stdio(0);
cin.tie(0);
int t=1;cin>>t;
while(t--) sol();
}
//g++ a.cpp -o a && a < in.txt > out.txt
Day 3
树状数组
#define ll long long
inline int lowbit(int x){return x&-x;}
struct BIT
{
ll tr[_mxn];
void add(int x,ll k)
{
for(int i=x;i<_mxn;i+=lowbit(i))
tr[i]+=k;
}
ll query(int l,int r)
{
ll res=0;
for(int i=r;i>0;i-=lowbit(i))
res+=tr[i];
for(int i=l-1;i>0;i-=lowbit(i))
res-=tr[i];
return res;
}
}tr;
二维树状数组
#define ll long long
inline int lowbit(int x){return x&-x;}
struct BIT
{
ll tr[_mxn][_mxn];
void add(int x,int y,ll k)
{
for(int i=x;i<_mxn;i+=lowbit(i))
for(int j=y;j<_mxn;j+=lowbit(j))
tr[i][j]+=k;
}
ll query(int x1,int y1,int x2,int y2)
{
if(x1>x2)
swap(x1,x2);
if(y1>y2)
swap(y1,y2);
ll res=0;
for(int i=x2;i>0;i-=lowbit(i))
for(int j=y2;j>0;j-=lowbit(j))
res+=tr[i][j];
for(int i=x2;i>0;i-=lowbit(i))
for(int j=y1-1;j>0;j-=lowbit(j))
res-=tr[i][j];
for(int i=x1-1;i>0;i-=lowbit(i))
for(int j=y2;j>0;j-=lowbit(j))
res-=tr[i][j];
for(int i=x1-1;i>0;i-=lowbit(i))
for(int j=y1-1;j>0;j-=lowbit(j))
res+=tr[i][j];
return res;
}
}tr;
Day 3 模拟赛
A
题意
给定一个正整数 \(x(1\le x\le10^{100000})\),把这个数字分成两个非空子串,求两个子串代表数字和为偶数的分割方案数。
题解
暴力枚举分割点,然后加两个串最后一位,判断。
代码:
#define ll long long
const int _mxn=+5;
string s;
int main()
{
___();
cin>>s;
int ans=0;
for(int i=0;i<s.size()-1;i++)
{
if((s[i]+s[s.size()-1]-'0'-'0')%2==0)
ans++;
}
cout<<ans<<endl;
return 0;
}
B
题意
给定一个 \(n\times m\) 的仅包含 \(0\sim5\) 的整数的矩阵,求最大的不与 \(0\) 或边界相邻的同数字四连通块面积。
题解
先把所有和 \(0\) 或边界相邻的连通块染成 \(0\),然后把不同连通块染成不同色,计算并取最大值即可。
代码:
#define ll long long
const int _mxn=500+5;
int dx[]={0,1,0,-1},dy[]={1,0,-1,0};
int n,m;
int a[_mxn][_mxn];
int t[_mxn][_mxn],tot=0,cnt[_mxn*_mxn];
void dfs(int x,int y,int nw,int s)
{
t[x][y]=s;
for(int i=0;i<4;i++)
{
int tx=x+dx[i],ty=y+dy[i];
if(tx>=1&&tx<=n&&ty>=1&&ty<=m&&a[tx][ty]==nw&&t[tx][ty]!=s)
dfs(tx,ty,nw,s);
}
}
int main()
{
___();
cin>>n>>m;
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
{
char c;
cin>>c;
a[i][j]=t[i][j]=c-'0';
}
for(int i=0;i<=n+1;i++)//和 0 或边界相邻的染成 0
{
for(int j=0;j<=m+1;j++)
{
if(a[i][j]==0)
{
for(int d=0;d<4;d++)
{
int tx=i+dx[d],ty=j+dy[d];
if(tx>=1&&tx<=n&&ty>=1&&ty<=m&&a[tx][ty])
dfs(tx,ty,a[tx][ty],0);
}
}
}
}
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
a[i][j]=t[i][j],t[i][j]=0;
int ans=0;
for(int i=1;i<=n;i++)
{
for(int j=1;j<=m;j++)
{
if(a[i][j]&&!t[i][j])//不同连通块染成不同色
dfs(i,j,a[i][j],++tot);
}
}
for(int i=1;i<=n;i++)//统计每个颜色格子数
for(int j=1;j<=m;j++)
cnt[t[i][j]]++;
for(int i=1;i<=tot;i++)//求答案
ans=max(ans,cnt[i]);
cout<<ans<<endl;
return 0;
}
C
题意
给定 \(n(1\le n\le10^{5})\) 个点,每个点等级定义为横纵坐标均小于等于该点横纵坐标的点个数(不算该点),分别求 \(0\sim(n-1)\) 等级的点数量。点按纵坐标升序给出,纵坐标相等按横坐标升序给出。
题解
经典二维偏序。\(n\) 太大了,不能直接用二维树状数组。发现如果按照给出顺序,只需要用一维树状数组维护两个坐标,然后取前缀和较小值即可。最好加个离散化。
代码:
#define ll long long
const int _mxn=1e5+5,_mxx=1e6+5;
inline int lowbit(int x){return x&-x;}
struct BIT
{
ll tr[_mxn];
void add(int x,ll k)
{
for(int i=x;i<_mxn;i+=lowbit(i))
tr[i]+=k;
}
ll query(int l,int r)
{
ll res=0;
for(int i=r;i>0;i-=lowbit(i))
res+=tr[i];
for(int i=l-1;i>0;i-=lowbit(i))
res-=tr[i];
return res;
}
}tx,ty;
int n,x[_mxn],y[_mxn],ans[_mxn];
int ttx[_mxn],tty[_mxn],mx[_mxx],my[_mxx];
int main()
{
___();
cin>>n;
for(int i=1;i<=n;i++)
{
cin>>x[i]>>y[i];
ttx[i]=x[i],tty[i]=y[i];
}
//离散化
sort(ttx+1,ttx+n+1);
int nx=unique(ttx+1,ttx+n+1)-ttx-1;
int ny=unique(tty+1,tty+n+1)-tty-1;
for(int i=1;i<=nx;i++)
mx[ttx[i]]=i;
for(int i=1;i<=ny;i++)
my[tty[i]]=i;
for(int i=1;i<=n;i++)
x[i]=mx[x[i]],y[i]=my[y[i]];
//求答案
for(int i=1;i<=n;i++)
{
tx.add(x[i],1),ty.add(y[i],1);
ans[min(tx.query(1,x[i]),ty.query(1,y[i]))]++;
}
for(int i=1;i<=n;i++)
cout<<ans[i]<<endl;
return 0;
}
D
题意
给定长为 \(n\) 的序列 \(a\),求满足 \(1\le i<j<k\le n\) 且 \(a_i<a_j<a_k\) 的三元组 \((i,j,k)\) 个数。
题解
类似逆序对,求出每个数左侧比它小的数个数和右侧比它大的数个数,两个乘起来并累加就是答案。
代码:
#define ll long long
const int _mxn=30000+5,_mxa=1e9+5;
inline int lowbit(int x){return x&-x;}
struct BIT
{
map<int,ll> tr;//暴力 map 存储
void add(int x,ll k)
{
for(int i=x;i<_mxa;i+=lowbit(i))
tr[i]+=k;
}
ll query(int l,int r)
{
ll res=0;
for(int i=r;i>0;i-=lowbit(i))
res+=tr[i];
for(int i=l-1;i>0;i-=lowbit(i))
res-=tr[i];
return res;
}
}tr1,tr2;
int n,a[_mxn];
ll l[_mxn],r[_mxn];
int main()
{
___();
cin>>n;
for(int i=1;i<=n;i++)
{
cin>>a[i];
tr1.add(a[i],1);
l[i]=tr1.query(1,a[i]-1);
}
for(int i=n;i>=1;i--)
{
tr2.add(a[i],1);
r[i]=tr2.query(a[i]+1,1e9);
}
ll ans=0;
for(int i=1;i<=n;i++)
ans+=l[i]*r[i];
cout<<ans<<endl;
return 0;
}
E
题意
给定长为 \(n\) 的序列 \(a\),定义 \(f(l,r,x)=\sum_{i=l}^{r}[a_i=x](1\le l\le r\le n)\),其中 \([]\) 为艾佛森括号,其中条件满足为 \(1\),否则为 \(0\)。求满足 \(1\le i<j\le n\) 且 \(f(1,i,a_i)>f(j,n,a_j)\) 的二元组 \((i,j)\) 个数。
题解
先离散化一下。可以用树状数组求 \(f(1,i,a_i),f(j,n,a_j)\) 的值,还可以用类似逆序对的思路直接求出答案。具体看代码:
#define ll long long
const int _mxn=1e6+5,_mxa=1e9+5;
inline int lowbit(int x){return x&-x;}
struct BIT
{
ll tr[_mxn];
void add(int x,ll k)
{
for(int i=x;i<_mxn;i+=lowbit(i))
tr[i]+=k;
}
ll query(int l,int r)
{
ll res=0;
for(int i=r;i>0;i-=lowbit(i))
res+=tr[i];
for(int i=l-1;i>0;i-=lowbit(i))
res-=tr[i];
return res;
}
}tri,trj,tr;
int n,a[_mxn],t[_mxn];
map<int,int> ma;
int fi[_mxn],fj[_mxn];
int main()
{
___();
cin>>n;
for(int i=1;i<=n;i++)
{
cin>>a[i];
t[i]=a[i];
}
//离散化
sort(t+1,t+n+1);
int nt=unique(t+1,t+n+1)-t-1;
for(int i=1;i<=nt;i++)
ma[t[i]]=i;
for(int i=1;i<=n;i++)
a[i]=ma[a[i]];
for(int i=1;i<=n;i++)
{
tri.add(a[i],1);
fi[i]=tri.query(a[i],a[i]);//前面的 a[i] 个数
}
for(int j=n;j>=1;j--)
{
trj.add(a[j],1);
fj[j]=trj.query(a[j],a[j]);//后面的 a[j] 个数
}
ll ans=0;
// for(int i=1;i<=n;i++)//暴力
// for(int j=i+1;j<=n;j++)
// if(fi[i]>fj[j])
// ans++;
for(int i=n;i>=1;i--)
{
ans+=tr.query(1,fi[i]-1);//后面的比 fi[i] 小的 fj[] 的个数
tr.add(fj[i],1);
}
cout<<ans<<endl;
return 0;
}
/*
i<j,fi[i]>fj[j]
*/
Day 4
线段树
const int _mxn=1e5+5;
int n,a[_mxn];
struct segtree
{
struct node
{
int l,r;
ll dat,add;
int len(){return r-l+1;}
}tr[_mxn<<2];
inline int ls(int p){return p<<1;}
inline int rs(int p){return p<<1|1;}
void pushup(int p)
{
tr[p].dat=tr[ls(p)].dat+tr[rs(p)].dat;
}
void build(int p,int l,int r)
{
tr[p].l=l,tr[p].r=r;
tr[p].add=0;
if(l==r)
{
tr[p].dat=a[l];
return;
}
int mid=(l+r)>>1;
build(ls(p),l,mid);
build(rs(p),mid+1,r);
pushup(p);
}
void pushdown(int p)
{
tr[ls(p)].dat+=tr[p].add*tr[ls(p)].len();
tr[ls(p)].add+=tr[p].add;
tr[rs(p)].dat+=tr[p].add*tr[rs(p)].len();
tr[rs(p)].add+=tr[p].add;
tr[p].add=0;
}
void update(int p,int l,int r,ll k)
{
if(l<=tr[p].l&&tr[p].r<=r)
{
tr[p].dat+=k*tr[p].len();
tr[p].add+=k;
return;
}
pushdown(p);
int mid=(tr[p].l+tr[p].r)>>1;
if(l<=mid)
update(ls(p),l,r,k);
if(r>mid)
update(rs(p),l,r,k);
pushup(p);
}
ll query(int p,int l,int r)
{
if(l<=tr[p].l&&tr[p].r<=r)
return tr[p].dat;
pushdown(p);
int mid=(tr[p].l+tr[p].r)>>1;
ll res=0;
if(l<=mid)
res+=query(ls(p),l,r);
if(r>mid)
res+=query(rs(p),l,r);
return res;
}
}tr;
Day 4 模拟赛
A
题意
给定一个边权全为 \(1\) 的有向图,求 \(1\) 号点到每个点的最短路,有点到不了输出 \(-1\)。
题解
写个 bfs 即可。
代码:
#define ll long long
const int _mxn=1e5+5;
vector<int> g[_mxn];
void add(int u,int v){g[u].push_back(v);}
int n,m,ans[_mxn];
bool vis[_mxn];
void bfs()
{
queue<int> q;
q.push(1);
vis[1]=true;
ans[1]=0;
while(!q.empty())
{
int nw=q.front();q.pop();
for(int it:g[nw])
{
if(!vis[it])
{
q.push(it);
vis[it]=true;
ans[it]=ans[nw]+1;
}
}
}
}
int main()
{
___();
cin>>n>>m;
while(m--)
{
int u,v;
cin>>u>>v;
add(u,v);
}
memset(ans,0x3f,sizeof(ans));
bfs();
for(int i=1;i<=n;i++)
cout<<(ans[i]==0x3f3f3f3f?-1:ans[i])<<" ";
cout<<endl;
return 0;
}
B
题意
有 \(a\) 颗红色种子,\(b\) 颗蓝色,\(c\) 颗绿色,\(x\) 颗红色可以转化为 \(1\) 颗蓝色,\(y\) 颗蓝色可以转化为 \(1\) 颗绿色,只能单向转化。一个套装包含三种种子各 \(1\) 颗,求最多可以凑成的套装数。
题解
二分答案。check 写法:绿不够用蓝凑,蓝不够用红凑,最后判红够不够即可。
代码:
#define ll long long
const int _mxn=+5;
ll a,b,c,x,y;
bool check(ll mid)
{
ll ta=a,tb=b,tc=c;
tb-=max(mid-tc,0ll)*y;
ta-=max(mid-tb,0ll)*x;
return ta>=mid;
}
int main()
{
___();
int _;
cin>>_;
while(_--)
{
cin>>a>>b>>c>>x>>y;
ll l=0,r=1e9,ans;
while(l<=r)
{
ll mid=(l+r)>>1;
if(check(mid))
l=mid+1,ans=mid;
else
r=mid-1;
}
cout<<ans<<endl;
}
return 0;
}
C
题意
给定一个序列,要求支持区间异或上一个数和求区间和。
题解
必定是使用线段树,但是区间异或不能直接维护。可以用线段树 tr[p].dat[k]
存 \(p\) 号节点区间里数二进制第 \(k\) 位 \(1\) 的个数,修改时遍历参数的二进制位,为 \(1\) 就取反区间里数的对应位,\(1\) 的个数就变成了原本 \(0\) 的个数,即 tr[p].dat[k]=tr[p].len-tr[p].dat[k]
。求答案时枚举位,累加 \(1\) 的个数乘上对应位的权值(\(2^k\))即可。
代码:
#define ll long long
const int _mxn=1e5+5,_mxa=1e6;
int n;
ll a[_mxn];
struct segtree
{
struct node
{
int l,r;
ll dat[22];
ll tag;
int len(){return r-l+1;}
}tr[_mxn<<2];//tr[i].dat[k]: i 节点的区间中数二进制第 k 位 1 的个数
inline int ls(int p){return p<<1;}
inline int rs(int p){return p<<1|1;}
void pushup(int p)
{
for(int k=0;k<21;k++)
tr[p].dat[k]=tr[ls(p)].dat[k]+tr[rs(p)].dat[k];
}
void build(int p,int l,int r)
{
tr[p].l=l,tr[p].r=r;
tr[p].tag=0;
if(l==r)
{
for(int k=0;k<21;k++)
tr[p].dat[k]=(a[l]>>k&1);
return;
}
int mid=(l+r)>>1;
build(ls(p),l,mid);
build(rs(p),mid+1,r);
pushup(p);
}
void pushdown(int p)
{
for(int k=0;k<21;k++)
if(tr[p].tag>>k&1)
{
tr[ls(p)].dat[k]=tr[ls(p)].len()-tr[ls(p)].dat[k];
tr[rs(p)].dat[k]=tr[rs(p)].len()-tr[rs(p)].dat[k];
}
tr[ls(p)].tag^=tr[p].tag;
tr[rs(p)].tag^=tr[p].tag;
tr[p].tag=0;
}
void update(int p,int l,int r,ll x)
{
if(l<=tr[p].l&&tr[p].r<=r)
{
for(int k=0;k<21;k++)
if(x>>k&1)
tr[p].dat[k]=tr[p].len()-tr[p].dat[k];
tr[p].tag^=x;
return;
}
pushdown(p);
int mid=(tr[p].l+tr[p].r)>>1;
if(l<=mid)
update(ls(p),l,r,x);
if(r>mid)
update(rs(p),l,r,x);
pushup(p);
}
ll query(int p,int l,int r)
{
if(l<=tr[p].l&&tr[p].r<=r)
{
ll res=0;
for(int k=0;k<21;k++)
res+=(1<<k)*tr[p].dat[k];
return res;
}
pushdown(p);
int mid=(tr[p].l+tr[p].r)>>1;
ll res=0;
if(l<=mid)
res+=query(ls(p),l,r);
if(r>mid)
res+=query(rs(p),l,r);
return res;
}
}tr;
int main()
{
___();
cin>>n;
for(int i=1;i<=n;i++)
cin>>a[i];
tr.build(1,1,n);
int m;
cin>>m;
while(m--)
{
int op;
cin>>op;
if(op==1)
{
int l,r;
cin>>l>>r;
cout<<tr.query(1,l,r)<<endl;
}
else
{
int l,r;
ll x;
cin>>l>>r>>x;
tr.update(1,l,r,x);
}
}
return 0;
}
D
题意
给定 \(n,m\) 和 \(n\) 条通道,通过第 \(i\) 条可以在 \([l_i,r_i]\) 中的任意两个坐标间传送,通道能量值为 \(w_i\)。选出一部分通道(可以全选),使可以通过这些通道从 \(1\) 到 \(m\),代价为这些通道最大能量值和最小能量值之差,求满足要求需要的最小代价。
题解(思路来自老师)
考虑枚举使用到的通道的最大能量 \(y\),假设通车时使用到的最小能量为 \(x\),那么必须满足 \(1\) 到 \(m\) 的每个坐标点都被一条能量在 \([x,y]\) 中的通道覆盖。发现对每个点只需记录覆盖了他的通道的最大能量,因为能量更小的与 \(y\) 差值一定更大。用线段树来维护最大能量,加入一条通道相当于给一个区间对这个通道能量取最大值。最小能量 \(x\) 即为全局最小值。
代码:
#define ll long long
const int _mxn=3e5+5,_mxm=1e6+5;
struct segtree
{
struct node
{
int l,r;
ll dat,tag;
int len(){return r-l+1;}
}tr[_mxm<<2];
inline int ls(int p){return p<<1;}
inline int rs(int p){return p<<1|1;}
void pushup(int p)
{
tr[p].dat=min(tr[ls(p)].dat,tr[rs(p)].dat);
}
void build(int p,int l,int r)
{
tr[p].l=l,tr[p].r=r;
tr[p].tag=0;
if(l==r)
{
tr[p].dat=0;
return;
}
int mid=(l+r)>>1;
build(ls(p),l,mid);
build(rs(p),mid+1,r);
pushup(p);
}
void pushdown(int p)
{
tr[ls(p)].dat=max(tr[ls(p)].dat,tr[p].tag);
tr[ls(p)].tag=max(tr[ls(p)].tag,tr[p].tag);
tr[rs(p)].dat=max(tr[rs(p)].dat,tr[p].tag);
tr[rs(p)].tag=max(tr[rs(p)].tag,tr[p].tag);
}
void update(int p,int l,int r,ll k)
{
if(l<=tr[p].l&&tr[p].r<=r)
{
tr[p].dat=max(tr[p].dat,k);
tr[p].tag=max(tr[p].tag,k);
return;
}
pushdown(p);
int mid=(tr[p].l+tr[p].r)>>1;
if(l<=mid)
update(ls(p),l,r,k);
if(r>mid)
update(rs(p),l,r,k);
pushup(p);
}
}tr;
struct node
{
int l,r,w;
}a[_mxn];
bool operator<(node _x,node _y)
{
return _x.w<_y.w;
}
int n,m;
int main()
{
___();
cin>>n>>m;
tr.build(1,1,m-1);
for(int i=1;i<=n;i++)
cin>>a[i].l>>a[i].r>>a[i].w;
sort(a+1,a+n+1);//按 w 升序排序
ll ans=1e9;
for(int i=1;i<=n;i++)
{
a[i].r--;
tr.update(1,a[i].l,a[i].r,a[i].w);
if(tr.tr[1].dat!=0)//开头有了
ans=min(ans,a[i].w-tr.tr[1].dat);
}
cout<<ans<<endl;
return 0;
}
E
题意
给定两个长为 \(n\) 的序列 \(a,b\),求满足 \(1\le l\le r\le n\) 且 \([l,r]\) 区间内 \(a\) 的最大值等于 \(b\) 的最小值的 \((l,r)\) 数对个数。
题解(思路来自老师)
打个 ST 表维护区间最大最小值,然后枚举右端点 \(r\)。注意到最大减最小是单调不降的,所以可以二分出区间最大减最小为 \(0\) 时 \(l\) 的最大和最小值,然后就可以求答案了。
代码咕咕咕,因为二分写假了还没调出来。
Day 5
并查集
struct dsu
{
int f[_mxn],siz[_mxn];
void init(int n)
{
for(int i=1;i<=n;i++)
f[i]=i,siz[i]=1;
}
int find(int x)
{
if(f[x]==x)
return x;
return find(f[x]);
}
void merge(int x,int y)
{
int fx=find(x),fy=find(y);
if(fx!=fy)
{
f[fy]=fx;
siz[fx]+=siz[fy];
}
}
bool same(int x,int y){return find(x)==find(y);}
int size(int x){return siz[find(x)];}
};
Kruskal 最小生成树
typedef ll w_type;
struct edge
{
int u,v;
w_type w;
edge(){}
edge(int _u,int _v,w_type _w):u(_u),v(_v),w(_w){}
bool operator<(edge x) const {return w<x.w;}
bool operator>(edge x) const {return w>x.w;}
};
vector<edge> e;
void add(int u,int v,w_type w){e.push_back(edge(u,v,w));}
w_type kruskal(int n)
{
dsu t;
t.init(n);
sort(e.begin(),e.end());
w_type res=0;
int cnt=0;
for(edge it:e)
{
if(!t.same(it.u,it.v))
{
t.merge(it.u,it.v);
res+=it.w;
cnt++;
if(cnt==n-1)
break;
}
}
if(cnt<n-1)
return -1;
return res;
}
Day 5 模拟赛 ACM
A
题意
有三个数 \(a,b,c\),三个线索 \(>\) 或 \(<\),分别表示:
- \(a,b\) 大小关系;
- \(a,c\) 大小关系;
- \(b,c\) 大小关系。
求哪个数最大,若矛盾无法判断输出 \(-1\)。
题解
签到题。
套一堆 if
判断即可。代码:(赛时两人一起打的,很乱)
#include<bits/stdc++.h>
using namespace std;
int main()
{
char a,b,c;
cin>>a>>b>>c;
if(a=='>')//a>b
{
if(b=='>')//a>c
{
cout<<"A"<<endl;//a 比两个都大
}
else//a<c
{//b<c 就 c 比两个都大;b>c 就矛盾
c=='<'?cout<<"C"<<endl:cout<<-1<<endl;
}
}
else//a<b
{
if(b=='>'){//a>c
if(c=='>')//b>c
cout<<"B"<<endl;//b 比两个都大
else
cout<<-1<<endl;//矛盾
}
else//a<c,a 最小
{
if(c=='>'){//b>c
cout<<"B"<<endl;
}
else//b<c
cout<<"C"<<endl;
}
}
return 0;
}
B
题意
给定一个长 \(n\) 的序列 \(a\),可以把整个序列的求和运算(即 \(a_1+a_2+\cdots+a_n\))中的最多一个加号变成乘号,求操作后答案的最大值。
题解
签到题。
暴力枚举变哪个,取最大值即可。记得开 long long。
代码:
#define ll long long
const int _mxn=1e5+5;
int n;
ll a[_mxn],s[_mxn];
int main()
{
___();
cin>>n;
for(int i=1;i<=n;i++)
cin>>a[i],s[i]=s[i-1]+a[i];
ll ans=s[n];//打的有点急懒得改了,直接用前缀和数组算总和了。
for(int i=2;i<=n;i++)
{
ans=max(ans,s[n]-a[i-1]-a[i]+a[i-1]*a[i]);//减掉两个再加上两个的积。
}
cout<<ans<<endl;
return 0;
}
C
题意
有 \(n\) 个人手上有一些卡牌,有 \(m\) 次操作 \((u,v)\),把 \(u\) 的卡牌给 \(v\) 一张。求经过 \(m\) 次操作后卡牌比原来多的人数。
题解
签到题。
模拟一下就行了,不用管原来有多少。
代码:
#define ll long long
const int _mxn=2e5+5;
int n,a[_mxn],t[_mxn];
int main()
{
___();
int m;
cin>>n>>m;
while(m--)
{
int u,v;
cin>>u>>v;
t[u]--,t[v]++;
}
int ans=0;
for(int i=1;i<=n;i++)
if(t[i]>a[i])
ans++;
cout<<ans<<endl;
return 0;
}
D
题意
定义字符串的多样性 \(d(s)\) 为字符串 \(s\) 中的不同字符种类数。给定仅包含小写字母的字符串 \(s\),求 \(d(s)\) 和 \(s\) 的子串中 \(d(s')\) 分别为 \(1,2,\cdots,d(s)\) 的子串数量。
题解
双指针。建立一个桶数组存储区间内每个字母出现次数,实时更新多样性。对于每个 \(i\in\{1,2,\cdots,d(s)\}\),枚举区间右端点 \(r\),找到使多样性为 \(i\) 的最小左端点 \(l\),记为 \(f_{r,i}\),答案即为 \(f_{r,i-1}-f{r,i}\)。
代码:
#define ll long long
const int _mxn=3e5+5;
string s;
int t[30],k;
int f[_mxn][30];//f[r][j] 表示右端点为 r 时使多样性为 j 的最小 l
int main()
{
___();
cin>>s;
int n=s.size();
s=' '+s;
for(int i=1;i<=n;i++)
{
if(!t[s[i]-'a'])
t[s[i]-'a']++,k++;
f[i][0]=i+1;
}
cout<<k<<endl;
for(int i=1;i<=k;i++)
{
memset(t,0,sizeof(t));
int sum=0;
ll ans=0;
for(int l=1,r=1;r<=n;r++)
{
int c=s[r]-'a';
if(!t[c])
sum++;
t[c]++;
while(sum>i)
{
if(t[s[l]-'a']==1)
sum--;
t[s[l]-'a']--;
l++;
}
f[r][i]=l;
if(sum==i)
ans+=f[r][i-1]-f[r][i];
}
cout<<ans<<endl;
}
return 0;
}
E
题意
给定一个 \(n\) 个顶点的加权树。查询 \(m\) 次,第 \(i\) 个查询以整数 \(q_i\) 给出,每次查询需要计算满足 \(u<v\) 且 \(u\to v\) 的简单路径上最大边权 \(\le q_i\) 的顶点对 \((u,v)\) 的数量。
题解
思路不会,直接贴赛后订的代码:
#define ll long long
const int _mxn=2e5+5;
ll p;
struct dsu
{
int f[_mxn],siz[_mxn];
void init(int n)
{
for(int i=1;i<=n;i++)
f[i]=i,siz[i]=1;
}
int find(int x)
{
if(f[x]==x)
return x;
return f[x]=find(f[x]);
}
void merge(int x,int y)
{
int fx=find(x),fy=find(y);
if(fx!=fy)
{
f[fx]=fy;
p-=1ll*siz[fx]*(siz[fx]-1)/2;
p-=1ll*siz[fy]*(siz[fy]-1)/2;
siz[fy]+=siz[fx];
p+=1ll*siz[fy]*(siz[fy]-1)/2;
}
}
bool same(int x,int y){return find(x)==find(y);}
int size(int x){return siz[find(x)];}
}st;
int n,m;
struct node
{
int u,v,w,id;
node(){}
node(int _u,int _v,int _w,int _id):u(_u),v(_v),w(_w),id(_id){}
};
vector<node> a;
bool cmp(node _x,node _y)
{
if(_x.w!=_y.w)
return _x.w<_y.w;
return _x.id<_y.id;
}
ll ans[_mxn];
int main()
{
___();
cin>>n>>m;
st.init(n);
for(int i=2;i<=n;i++)
{
int u,v,w;
cin>>u>>v>>w;
a.push_back(node(u,v,w,0));
}
for(int i=1;i<=m;i++)
{
int x;
cin>>x;
a.push_back(node(0,0,x,i));
}
sort(a.begin(),a.end(),cmp);
for(auto it:a)
{
if(it.id)
ans[it.id]=p;
else
st.merge(it.u,it.v);
}
for(int i=1;i<=m;i++)
cout<<ans[i]<<" ";
cout<<endl;
return 0;
}
F
题意
给定一个 \(n\times n(1\le n\le 2000)\) 的矩阵,描述的是一棵树的信息,树上每条边都有一个正权重,矩阵的每个数字代表一棵树上两个点之间的路径的权重和。判断这个矩阵是否是一棵树的合法信息。
题解
首先合法一定要 \(d_{i,j}=d_{j,i}\),先判一下这个。把给出的矩阵看成一个完全图的邻接矩阵,显然树是该图的最小生成树,因为图的边权都是正的。然后枚举每两个点,求两点在树上的距离,如果全都等于图上两点间的边权就是对的,反之不对。求距离可以用 LCA,随便找个点作为根,预处理出每个点到根的距离 \(\operatorname{dis}(u)\),\(u\to v\) 的距离即为 \(\operatorname{dis}(u)+\operatorname{dis}(v)-2\times\operatorname{dis}(\operatorname{LCA}(u,v))\)。但是 \(n\) 只有 \(2000\),也可以直接爆搜算距离。
代码:
#define int long long
const int _mxn=2000+5;
struct dsu
{
int f[_mxn],siz[_mxn];
void init(int n)
{
for(int i=1;i<=n;i++)
f[i]=i,siz[i]=1;
}
int find(int x)
{
if(f[x]==x)
return x;
return find(f[x]);
}
void merge(int x,int y)
{
int fx=find(x),fy=find(y);
if(fx!=fy)
{
f[fy]=fx;
siz[fx]+=siz[fy];
}
}
bool same(int x,int y){return find(x)==find(y);}
int size(int x){return siz[find(x)];}
}st;
typedef int w_type;
struct graph
{
struct node
{
int v;
w_type w;
node(){}
node(int _v,w_type _w):v(_v),w(_w){}
bool operator<(node x) const {return w<x.w;}
bool operator>(node x) const {return w>x.w;}
};
struct edge
{
int u,v;
w_type w;
edge(){}
edge(int _u,int _v,w_type _w):u(_u),v(_v),w(_w){}
bool operator<(edge x) const {return w<x.w;}
bool operator>(edge x) const {return w>x.w;}
};
vector<node> g[_mxn];
vector<edge> e;
void add(int u,int v,w_type w)
{
g[u].push_back(node(v,w));
e.push_back(edge(u,v,w));
}
}g,tr;
w_type kruskal(int n)
{
dsu t;
t.init(n);
sort(g.e.begin(),g.e.end());
w_type res=0;
int cnt=0;
for(auto it:g.e)
{
if(!t.same(it.u,it.v))
{
t.merge(it.u,it.v);
tr.add(it.u,it.v,it.w);
tr.add(it.v,it.u,it.w);
res+=it.w;
cnt++;
if(cnt==n-1)
break;
}
}
if(cnt<n-1)
return -1;
return res;
}
namespace LCA
{
int deep[_mxn],f[_mxn][30],lg;
void init(int u,int fa,int dep)
{
deep[u]=dep;
f[u][0]=fa;
for(int i=1;i<=lg;i++)
f[u][i]=f[f[u][i-1]][i-1];
for(auto it:tr.g[u])
{
if(it.v==fa)
continue;
init(it.v,u,dep+1);
}
}
int query(int x,int y)
{
if(deep[x]<deep[y])
swap(x,y);
for(int i=lg;i>=0;i--)
if(deep[f[x][i]]>=deep[y])
x=f[x][i];
if(x==y)
return x;
for(int i=lg;i>=0;i--)
if(f[x][i]!=f[y][i])
x=f[x][i],y=f[y][i];
return f[x][0];
}
};
int n,d[_mxn][_mxn];
int dis[_mxn];
void dfs(int u,int fa)
{
for(auto it:tr.g[u])
{
if(it.v==fa)
continue;
dis[it.v]=dis[u]+it.w;
dfs(it.v,u);
}
}
signed main()
{
___();
cin>>n;
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
{
cin>>d[i][j];
if(j>i)
g.add(i,j,d[i][j]);
}
for(int i=1;i<=n;i++)
{
if(d[i][i])//自己和自己距离不为 0 就不对
{
cout<<"NO"<<endl;
return 0;
}
for(int j=i+1;j<=n;j++)
{
if(d[i][j]!=d[j][i])
{
cout<<"NO"<<endl;
return 0;
}
}
}
if(kruskal(n)==-1)//存最小生成树
{
cout<<"NO"<<endl;
return 0;
}
LCA::lg=log2(n);//初始化 LCA
LCA::init(1,0,1);
dfs(1,0);//初始化 dis
for(int i=1;i<=n;i++)
{
for(int j=i+1;j<=n;j++)
{
if(d[i][j]!=dis[i]+dis[j]-2*dis[LCA::query(i,j)])
{
cout<<"NO"<<endl;
return 0;
}
}
}
cout<<"YES"<<endl;
return 0;
}
G
题意
给定一个长为 \(n\) 的由小写字母组成的字符串和 \(q\) 次操作,每个操作有两种:
1 l r d
:区间 \(s[l\dots r]\) 中每个字符后移 \(d\) 次。后移:将字符变为字母表的下一个字母;特别地,z
变为a
。2 l r
:查询区间 \(s[l\dots r]\) 经过重新排列能否组成回文串。
题解
用线段树维护每个区间每个字母的数量。发现一个字符串如果能重组成回文串,则:
- 长为奇数:出现次数为奇数的字母数量为 \(1\);
- 长为偶数:出现次数为奇数的字母数量为 \(0\)。
直接计数接判断即可。
代码:
#define ll long long
const int _mxn=1e5+5;
int n;
string s;
struct segtree
{
struct node
{
int l,r;
int dat[26],add;
int len(){return r-l+1;}
}tr[_mxn<<2];//tr[p].dat[k]:p 节点的区间 k 字母的出现次数
inline int ls(int p){return p<<1;}
inline int rs(int p){return p<<1|1;}
void pushup(int p)
{
for(int i=0;i<26;i++)
tr[p].dat[i]=tr[ls(p)].dat[i]+tr[rs(p)].dat[i];
}
void build(int p,int l,int r)
{
tr[p].l=l,tr[p].r=r;
tr[p].add=0;
for(int i=0;i<26;i++)
tr[p].dat[i]=0;
if(l==r)
{
tr[p].dat[s[l-1]-'a']=1;
return;
}
int mid=(l+r)>>1;
build(ls(p),l,mid);
build(rs(p),mid+1,r);
pushup(p);
}
void chg(int p,int d)
{
int tmp[26];
for(int k=0;k<26;k++)
tmp[k]=tr[p].dat[k];
for(int k=0;k<26;k++)
tr[p].dat[(k+d%26)%26]+=tmp[k];//后移 d 次后对应的字母出现次数加原字母出现次数
for(int k=0;k<26;k++)
tr[p].dat[k]-=tmp[k];//移完原来的减掉
(tr[p].add+=d%26)%=26;
}
void pushdown(int p)
{
chg(ls(p),tr[p].add);
chg(rs(p),tr[p].add);
tr[p].add=0;//一定要清零 tag,不然爆零两行泪
}
void update(int p,int l,int r,int d)
{
if(l<=tr[p].l&&tr[p].r<=r)
{
chg(p,d);
return;
}
pushdown(p);
int mid=(tr[p].l+tr[p].r)>>1;
if(l<=mid)
update(ls(p),l,r,d);
if(r>mid)
update(rs(p),l,r,d);
pushup(p);
}
vector<int> query(int p,int l,int r)
{
if(l<=tr[p].l&&tr[p].r<=r)
{
vector<int> res;
for(int k=0;k<26;k++)
res.push_back(tr[p].dat[k]);
return res;
}
pushdown(p);
int mid=(tr[p].l+tr[p].r)>>1;
vector<int> res(26);
if(l<=mid)
{
vector<int> lans=query(ls(p),l,r);
for(int i=0;i<26;i++)
res[i]+=lans[i];
}
if(r>mid)
{
vector<int> rans=query(rs(p),l,r);
for(int i=0;i<26;i++)
res[i]+=rans[i];
}
return res;
}
}tr;
int main()
{
___();
int _;
cin>>_;
while(_--)
{
int n,q;
cin>>n>>q;
cin>>s;
tr.build(1,1,n);
while(q--)
{
int op;
cin>>op;
if(op==1)
{
int l,r,d;
cin>>l>>r>>d;
tr.update(1,l,r,d%26);
}
else
{
int l,r;
cin>>l>>r;
int len=r-l+1,cnto=0;
vector<int> res;
res=tr.query(1,l,r);
for(int i=0;i<26;i++)
{
if(res[i]%2==1)
cnto++;
}
if(len%2==cnto)//判断
cout<<"Yes"<<endl;
else
cout<<"No"<<endl;
}
}
}
return 0;
}
H
题意
维护一行 \(n\) 个花盆,共有 \(m\) 个事件,事件有两种:
- 园丁在 \([l,r]\) 区间种下新品种花苗;
- 询问在 \([l,r]\) 区间内,有多少种不同的花苗。
一个花盆可以种多株花苗。
题解
不会思路,直接贴赛后订的代码:
#define ll long long
const int _mxn=50000+5;
inline int lowbit(int x){return x&-x;}
struct BIT
{
ll tr[_mxn];
void add(int x,ll k)
{
for(int i=x;i<_mxn;i+=lowbit(i))
tr[i]+=k;
}
ll query(int l,int r)
{
ll res=0;
for(int i=r;i>0;i-=lowbit(i))
res+=tr[i];
for(int i=l-1;i>0;i-=lowbit(i))
res-=tr[i];
return res;
}
}trl,trr;
int n,m;
int main()
{
___();
cin>>n>>m;
while(m--)
{
int op,l,r;
cin>>op>>l>>r;
if(op==1)
{
trl.add(l,1);
trr.add(r,1);
}
else
{
cout<<trl.query(1,r)-trr.query(1,l-1)<<endl;
}
}
return 0;
}
Day 6
ST 表
struct ST
{
int fmx[_mxn][30],fmn[_mxn][30];
void initmax(int *a)
{
for(int i=1;i<=n;i++)
fmx[i][0]=a[i];
int t=log2(n);
for(int j=1;j<=t;j++)
for(int i=1;i<=n-(1<<j)+1;i++)
fmx[i][j]=max(fmx[i][j-1],fmx[i+(1<<(j-1))][j-1]);
}
void initmin(int *a)
{
for(int i=1;i<=n;i++)
fmn[i][0]=a[i];
int t=log2(n);
for(int j=1;j<=t;j++)
for(int i=1;i<=n-(1<<j)+1;i++)
fmn[i][j]=min(fmn[i][j-1],fmn[i+(1<<(j-1))][j-1]);
}
void init(int *a)
{
initmax(a);
initmin(a);
}
int qmax(int l,int r)
{
int t=log2(r-l+1);
return max(fmx[l][t],fmx[r-(1<<t)+1][t]);
}
int qmin(int l,int r)
{
int t=log2(r-l+1);
return min(fmn[l][t],fmn[r-(1<<t)+1][t]);
}
}st;
LCA
namespace LCA
{
int deep[_mxn],f[_mxn][30],lg;
void init(int u,int fa,int dep)
{
deep[u]=dep;
f[u][0]=fa;
for(int i=1;i<=lg;i++)
f[u][i]=f[f[u][i-1]][i-1];
for(auto it:g[u])
{
if(it==fa)
continue;
init(it,u,dep+1);
}
}
int query(int x,int y)
{
if(deep[x]<deep[y])
swap(x,y);
for(int i=lg;i>=0;i--)
if(deep[f[x][i]]>=deep[y])
x=f[x][i];
if(x==y)
return x;
for(int i=lg;i>=0;i--)
if(f[x][i]!=f[y][i])
x=f[x][i],y=f[y][i];
return f[x][0];
}
}
Day 6 模拟赛
A
题意
有 \(n\) 颗山楂,第 \(i\) 颗山楂的编号为 \(a_i\),从第 \(1\) 颗到第 \(n\) 颗山楂的顺序开始串糖葫芦,新串上的山楂会串到上一颗山楂的后面。如果糖葫芦山楂数大于 \(1\) 且两端山楂编号相同,这串糖葫芦就串好了,之后重新开始串一串新的。求串完所有山楂能串好多少串糖葫芦。
题解
用一个指针标记开头然后模拟即可。代码:
const int _mxn=100000+5;
int n,a[_mxn];
int main()
{
___();
cin>>n;
int ans=0,l=1;
for(int i=1;i<=n;i++)
{
cin>>a[i];
if(i!=l&&a[i]==a[l])
ans++,l=i+1;
}
cout<<ans<<endl;
return 0;
}
B
题意
有 \(n\) 个星球,每个星球有一个能量频率 \(a_i\)。初始飞船在 \(1\) 号星球,所有星球都未激活。激活 \(i\) 号星球要消耗 \(a_i\) 点能量,飞船可以不消耗能量在已激活星球间任意移动,或消耗 \(\operatorname{lcm}(a_i,a_j)\) 能量从已激活星球 \(i\) 到未激活星球 \(j\)。求激活所有星球需要的最少能量。
题解
首先答案肯定包含激活所有星球需要的能量。考虑建图,显然只要图连通,边权和即为移动消耗的能量。建成一个完全图,连接 \((u,v)\) 点的边权是 \(\operatorname{lcm}(a_u,a_v)\),那么答案即为该图的最小生成树的边权和。用 Kruskal 或 Prim 均可,我写的 Kruskal。
代码:
#define ll long long
const int _mxn=2e3+5;
int n,a[_mxn];
int lcm(int x,int y){return x*y/__gcd(x,y);}
struct graph
{
typedef int w_type;
struct node
{
int v;
w_type w;
node(){}
node(int _v,w_type _w):v(_v),w(_w){}
bool operator<(node x) const {return w<x.w;}
bool operator>(node x) const {return w>x.w;}
};
struct edge
{
int u,v;
w_type w;
edge(){}
edge(int _u,int _v,w_type _w):u(_u),v(_v),w(_w){}
bool operator<(edge x) const {return w<x.w;}
bool operator>(edge x) const {return w>x.w;}
};
vector<node> g[_mxn];
vector<edge> e;
void add(int u,int v,w_type w)
{
g[u].push_back(node(v,w));
e.push_back(edge(u,v,w));
}
}g;
struct dsu
{
int f[_mxn],siz[_mxn];
void init(int n)
{
for(int i=1;i<=n;i++)
f[i]=i,siz[i]=1;
}
int find(int x)
{
if(f[x]==x)
return x;
return find(f[x]);
}
void merge(int x,int y)
{
int fx=find(x),fy=find(y);
if(fx!=fy)
{
f[fy]=fx;
siz[fx]+=siz[fy];
}
}
bool same(int x,int y){return find(x)==find(y);}
int size(int x){return siz[find(x)];}
};
int kruskal(int n)
{
dsu t;
t.init(n);
sort(g.e.begin(),g.e.end());
int res=0;
int cnt=0;
for(auto it:g.e)
{
if(!t.same(it.u,it.v))
{
t.merge(it.u,it.v);
res+=it.w;
cnt++;
if(cnt==n-1)
break;
}
}
if(cnt<n-1)
return -1;
return res;
}
int main()
{
___();
cin>>n;
int sum=0;
for(int i=1;i<=n;i++)
cin>>a[i],sum+=a[i];
for(int i=1;i<=n;i++)
{
for(int j=1;j<=n;j++)
{
if(i==j)
continue;
g.add(i,j,lcm(a[i],a[j]));
}
}
cout<<sum+kruskal(n)<<endl;
return 0;
}
C
题意
给定一棵边带权的树,询问 \(m\) 次,每次两个人从两个节点向 \(1\) 号节点走,走一条边花费边权分钟,先走到两人共同路径的起点的人会开始等待直到另一个人到,求等待时间。
题解
“两人共同路径的起点”显然是 LCA。设两个人分别为 \(u,v\),\(\operatorname{dis}(u)\) 为 \(u\) 到根节点距离,则 \(u\to \operatorname{LCA}\) 的距离即为 \(\operatorname{dis}(u)-\operatorname{dis}(\operatorname{LCA})\),\(v\) 同理,答案即为 \(|(\operatorname{dis}(u)-\operatorname{dis}(\operatorname{LCA}))-(\operatorname{dis}(v)-\operatorname{dis}(\operatorname{LCA}))|=|\operatorname{dis}(u)-\operatorname{dis}(\operatorname{LCA})-\operatorname{dis}(v)+\operatorname{dis}(\operatorname{LCA})|=|\operatorname{dis}(u)-\operatorname{dis}(v)|\),我们惊奇的发现 \(\operatorname{dis}(\operatorname{LCA})\) 被消掉了,所以我们连 LCA 都不用求,直接预处理 \(\operatorname{dis}(u)\) 即可。
代码:
#define ll long long
const int _mxn=100000+5;
struct graph
{
typedef int w_type;
struct node
{
int v;
w_type w;
node(){}
node(int _v,w_type _w):v(_v),w(_w){}
bool operator<(node x) const {return w<x.w;}
bool operator>(node x) const {return w>x.w;}
};
struct edge
{
int u,v;
w_type w;
edge(){}
edge(int _u,int _v,w_type _w):u(_u),v(_v),w(_w){}
bool operator<(edge x) const {return w<x.w;}
bool operator>(edge x) const {return w>x.w;}
};
vector<node> g[_mxn];
vector<edge> e;
void add(int u,int v,w_type w)
{
g[u].push_back(node(v,w));
e.push_back(edge(u,v,w));
}
}g;
int n;
ll dis[_mxn];
void dfs(int u,int fa)//预处理 dis
{
for(auto it:g.g[u])
{
if(it.v==fa)
continue;
dis[it.v]=dis[u]+it.w;
dfs(it.v,u);
}
}
int main()
{
___();
cin>>n;
for(int i=1;i<n;i++)
{
int u,v,w;
cin>>u>>v>>w;
g.add(u,v,w),g.add(v,u,w);
}
dfs(1,0);
int m;
cin>>m;
while(m--)
{
int u,v;
cin>>u>>v;
cout<<abs(dis[u]-dis[v])<<endl;
}
return 0;
}
D
题意
给定长为 \(n\) 的序列 \(a\) 和一个非负整数 \(x\),询问 \(m\) 次,每次询问 \(a\) 中 \([l,r]\) 区间是否存在两个数异或等于 \(x\)。
题解
首先要使 \(a_i\oplus a_j=x\),那么 \(a_j=a_i\oplus x\)。我们可以对于每个 \(i\) 存储需要的 \(a_j\) 的最后出现位置,记作 \(s_i\)。询问时对 \(s\) 求区间最大值,如果这个值在区间内就有,否则没有。
代码:
const int _mxn=1e5+5;
int n,a[_mxn],lst[_mxn],t[1<<20];
struct ST
{
int fmx[_mxn][30],fmn[_mxn][30];
void initmax(int *a)
{
for(int i=1;i<=n;i++)
fmx[i][0]=a[i];
int t=log2(n);
for(int j=1;j<=t;j++)
for(int i=1;i<=n-(1<<j)+1;i++)
fmx[i][j]=max(fmx[i][j-1],fmx[i+(1<<(j-1))][j-1]);
}
void initmin(int *a)
{
for(int i=1;i<=n;i++)
fmn[i][0]=a[i];
int t=log2(n);
for(int j=1;j<=t;j++)
for(int i=1;i<=n-(1<<j)+1;i++)
fmn[i][j]=min(fmn[i][j-1],fmn[i+(1<<(j-1))][j-1]);
}
void init(int *a)
{
initmax(a);
initmin(a);
}
int qmax(int l,int r)
{
int t=log2(r-l+1);
return max(fmx[l][t],fmx[r-(1<<t)+1][t]);
}
int qmin(int l,int r)
{
int t=log2(r-l+1);
return min(fmn[l][t],fmn[r-(1<<t)+1][t]);
}
}st;
int m,x;
int main()
{
___();
cin>>n>>m>>x;
for(int i=1;i<=n;i++)
{
cin>>a[i];
int y=a[i]^x;
if(t[y])
lst[i]=t[y];
else
lst[i]=-1;
t[a[i]]=i;
}
st.init(lst);
while(m--)
{
int l,r;
cin>>l>>r;
cout<<(st.qmax(l,r)>=l?"yes":"no")<<endl;
}
return 0;
}
E
题意
[NOIP 2016 提高组] 天天爱跑步,洛谷 P1600。
题解(来自老师)
代码:
#define ll long long
const int _mxn=3e5+5;
vector<int> g[_mxn];
void add(int u,int v){g[u].push_back(v);}
int n,m,w[_mxn],ans[_mxn];
namespace LCA
{
int deep[_mxn],f[_mxn][30],lg;
void init(int u,int fa,int dep)
{
deep[u]=dep;
f[u][0]=fa;
for(int i=1;i<=lg;i++)
f[u][i]=f[f[u][i-1]][i-1];
for(auto it:g[u])
{
if(it==fa)
continue;
init(it,u,dep+1);
}
}
int query(int x,int y)
{
if(deep[x]<deep[y])
swap(x,y);
for(int i=lg;i>=0;i--)
if(deep[f[x][i]]>=deep[y])
x=f[x][i];
if(x==y)
return x;
for(int i=lg;i>=0;i--)
if(f[x][i]!=f[y][i])
x=f[x][i],y=f[y][i];
return f[x][0];
}
}
using LCA::f;
using LCA::deep;
vector<int> ins1[_mxn],ins2[_mxn],del1[_mxn],del2[_mxn];
map<int,int> mp1,mp2;
void dfs(int u,int fa)
{
ans[u]-=mp1[w[u]+deep[u]]+mp2[w[u]-deep[u]];
for(int it:ins1[u])
mp1[it]++;
for(int it:del1[u])
mp1[it]--;
for(int it:ins2[u])
mp2[it]++;
for(int it:del2[u])
mp2[it]--;
for(int it:g[u])
if(it!=fa)
dfs(it,u);
ans[u]+=mp1[w[u]+deep[u]]+mp2[w[u]-deep[u]];
}
int main()
{
___();
cin>>n>>m;
for(int i=1;i<n;i++)
{
int u,v;
cin>>u>>v;
add(u,v),add(v,u);
}
LCA::lg=log2(n),LCA::init(1,0,1);
for(int i=1;i<=n;i++)
cin>>w[i];
while(m--)
{
int u,v;
cin>>u>>v;
int lca=LCA::query(u,v);
if(deep[u]>=deep[lca])
{
ins1[u].push_back(deep[u]);
del1[f[lca][0]].push_back(deep[u]);
}
if(deep[v]>deep[lca])
{
ins2[v].push_back(deep[u]-2*deep[lca]);
del2[lca].push_back(deep[u]-2*deep[lca]);
}
}
dfs(1,0);
for(int i=1;i<=n;i++)
cout<<ans[i]<<" ";
cout<<endl;
return 0;
}
Day 7
Dijkstra
int dis[_mxn];
void dijkstra(int s)
{
memset(dis,0x3f,sizeof(dis));
typedef graph::node node;
priority_queue<node,vector<node>,greater<node> > q;
dis[s]=0;
q.push(node(s,0));
while(!q.empty())
{
node nw=q.top();q.pop();
int u=nw.v;
if(nw.w>dis[u])
continue;
for(auto it:g.g[u])
{
if(dis[u]+it.w<dis[it.v])
{
dis[it.v]=dis[u]+it.w;
q.push(node(it.v,dis[it.v]));
}
}
}
}
SPFA
int dis[_mxn],cnt[_mxn];
bool in[_mxn];
bool spfa(int s)
{
queue<int> q;
q.push(s),dis[s]=0,in[s]=true,cnt[s]=1;
while(!q.empty())
{
int u=q.front();q.pop();
in[u]=false;
for(auto it:g.g[u])
{
if(dis[u]+it.w<dis[it.v])
{
dis[it.v]=dis[nw]+w;
if(!in[it.v])
{
q.push(it.v),in[it.v]=true;
if(++cnt[it.v]>n)
return false;
}
}
}
}
return true;
}
Floyd
太简单了懒得贴了。
拓扑排序
- 遍历所有入度为 \(0\) 的点,加入队列;
- 取出队头,遍历队头指向的点,这些点入度 \(-1\),减完入度为 \(0\) 就入队并输出;
- 重复 2 直到队列为空。
代码:
int deg[_mxn],tdeg[_mxn];
bool toposort()
{
queue<int> q;
int cnt=0;
for(int i=1;i<=n;i++)
{
if((tdeg[i]=deg[i])==0)
{
q.push(i);
cnt++;
cout<<i<<" ";
}
}
int lv=0;
while(!q.empty())
{
int siz=q.size();
while(siz--)
{
int u=q.front();q.pop();
for(int it:g[u])
{
tdeg[it]--;
if(tdeg[it]==0)
{
q.push(it);
cnt++;
cout<<it<<" ";
}
}
}
lv++;
}
return cnt==n;
}
Day 7 模拟赛
A
题意
给定一个不超过 \(5\) 位的正整数 \(n\) 和一位正整数 \(m\),可以把 \(m\) 插入 \(n\) 的任意位置,包括开头结尾,求结果最大值。
题解
签到题。存成 string,枚举插入位置,插完的串为 n.substr(0,i)+m+n.substr(i,l-i)
,转成 int 取最大就行了。太简单了代码懒得放了。
B
题意
给定 \(n(2\le n\le10)\),求 \(1\sim n\) 的所有排列中,相邻两数之和均不为素数的排列个数。
题解
dfs 全排列然后直接判断就行了。太简单了代码懒得放了。
C
题意
给定三个正整数 \(n,m,b\) 和一张 \(n\) 个点 \(m\) 条边的边带权无向图,点有点权,求 \(1\sim n\) 的路径中 边权和 \(\le b\) 的路径上点权的最大值 的最小值。
(洛谷 P1462)
题解
给点权数组排个序,然后二分,check 里跑个 dijkstra,当前点点权如果 \(>mid\) 就不走,最后判断到 \(n\) 的最短路是否 \(\le b\) 即可。
代码:
#define ll long long
const int _mxn=1e4+5;
struct graph
{
typedef int w_type;
struct node
{
int v;
w_type w;
node(){}
node(int _v,w_type _w):v(_v),w(_w){}
bool operator<(node x) const {return w<x.w;}
bool operator>(node x) const {return w>x.w;}
};
struct edge
{
int u,v;
w_type w;
edge(){}
edge(int _u,int _v,w_type _w):u(_u),v(_v),w(_w){}
bool operator<(edge x) const {return w<x.w;}
bool operator>(edge x) const {return w>x.w;}
};
vector<node> g[_mxn];
vector<edge> e;
void add(int u,int v,w_type w)
{
g[u].push_back(node(v,w));
e.push_back(edge(u,v,w));
}
}g;
int n,m,b,f[_mxn],t[_mxn];
int dis[_mxn];
bool check(int mxf)//dijkstra 的 check
{
typedef graph::node node;
priority_queue<node,vector<node>,greater<node> > q;
memset(dis,0x3f,sizeof(dis));
dis[1]=0;
q.push(node(1,0));
while(!q.empty())
{
node nw=q.top();q.pop();
int u=nw.v;
if(nw.w>dis[u]||f[u]>mxf)
continue;
for(auto it:g.g[u])
{
if(f[it.v]>mxf)
continue;
if(dis[u]+it.w<dis[it.v])
{
dis[it.v]=dis[u]+it.w;
q.push(node(it.v,dis[it.v]));
}
}
}
return dis[n]<=b;
}
int main()
{
___();
cin>>n>>m>>b;
for(int i=1;i<=n;i++)
cin>>f[i],t[i]=f[i];
sort(t+1,t+n+1);
while(m--)
{
int u,v,w;
cin>>u>>v>>w;
g.add(u,v,w),g.add(v,u,w);
}
int l=1,r=n+1;
while(l<r)
{
int mid=(l+r)>>1;
if(check(t[mid]))
r=mid;
else
l=mid+1;
}
if(r>n)
cout<<"AFK"<<endl;
else
cout<<t[r]<<endl;
return 0;
}
D
题意
题解
老师的 std:
#include<bits/stdc++.h>
using namespace std;
#define MAX 1005
vector<int> e[MAX],E[MAX];
int deg[MAX];
int n,m,D,tot;
int ary[MAX],bg[MAX]; //拓扑序;起始节点
bool flg[MAX],vis[MAX];//一定发生,可能未发生,
bool ban[MAX],hped[MAX];
void ck(int id){ //判断id是否一定发生
memset(ban,0,sizeof(ban));
memset(hped,0,sizeof(hped));
ban[id]=1;
for(int i=n;i>=1;i--){
int x=ary[i];
if(ban[x]) for(auto y:E[x]) ban[y]=1;
}
// for(int i=1;i<=n;i++) cout<<ban[i]<<" ";
// cout<<endl;
for(int i=1;i<=n;i++){
int x=ary[i];
if(ban[x]) continue;
if(bg[x]) hped[x]=1;
if(hped[x])
for(auto y:e[x])
hped[y]=1;
}
for(int i=1;i<=n;i++){
int x=ary[i];
if(flg[x]){
if(ban[x]||!hped[x]){
flg[id]=1; return;
}
}
}
vis[id]=1;
for(int i=1;i<=n;i++)
if(ban[i]) vis[i]=1;
}
void sol(){
cin>>n>>m>>D;
for(int i=1,x,y;i<=m;i++){
cin>>x>>y;
e[x].push_back(y);
E[y].push_back(x);
deg[y]++;
}
for(int i=1,x;i<=D;i++){
cin>>x;flg[x]=1;
}
queue<int> q;
for(int i=1;i<=n;i++){
if(!deg[i]){
q.push(i);
bg[i]=1;
}
}
while(!q.empty()){
int x=q.front();q.pop();
ary[++tot]=x;
for(auto y:e[x]){
deg[y]--;
if(!deg[y]) q.push(y);
}
}
for(int i=1;i<=n;i++) if(!vis[i]){
if(!flg[i]) ck(i);
if(flg[i]) cout<<i<<" ";
}
//for(int i=1;i<=n;i++) cout<<flg[i]<<endl;
}
int main(){
ios::sync_with_stdio(0);
cin.tie(0);
int t=1;//cin>>t;
while(t--) sol();
}
E
题意
题解
显然 \(0\) 对答案没有贡献,所以先直接去掉。最小环最小显然只能是 \(3\),所以只要所有的数里有一个二进制位有三个 \(1\) 那么按位与后就会有至少三组数的与不为 \(0\)。数据范围一共 \(10^{18}\),也就是大约 \(2^{64}\)(虽然差了不少),那么根据抽屉原理,只要有大于 \(2\times64\) 个非零数,那么就会有至少一个二进制位有至少 \(3\) 个 \(1\),所以 \(n>128\) 时答案一直为 \(3\)。这样数据范围就被缩小到了 \(n\le128\),\(O(n^3)\) 可解,所以直接打个 Floyd 的最小环板子(洛谷 P6175)就行了。
代码:
#define ll long long
const int _mxn=1000+5;
int n;
vector<ll> a;
ll g[_mxn][_mxn],dis[_mxn][_mxn];//因为神秘原因,不开 long long 会 95 分 WA
ll ans=1e18;
int main()
{
___();
cin>>n;
for(int i=1;i<=n;i++)
{
ll x;
cin>>x;
if(x)//去掉 0
a.push_back(x);
}
n=a.size();//更新 n
if(n<=128)//打个 Floyd 最小环板子
{
memset(g,0x1f,sizeof(g));
memset(dis,0x1f,sizeof(dis));
for(int i=1;i<=n;i++)
g[i][i]=dis[i][i]=0;
for(int i=1;i<=n;i++)
for(int j=i+1;j<=n;j++)
if(a[i-1]&a[j-1])
g[i][j]=g[j][i]=dis[i][j]=dis[j][i]=1;
for(int k=1;k<=n;k++)
{
for(int i=1;i<k;i++)
for(int j=i+1;j<k;j++)
ans=min(ans,dis[i][j]+g[i][k]+g[k][j]);
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
dis[i][j]=min(dis[i][j],dis[i][k]+dis[k][j]);
}
cout<<(ans==1e18?-1:ans)<<endl;
}
else
cout<<3<<endl;
return 0;
}
/*
1&1=1
1&0=0
0&0=0
*/
Day 8
线性 DP 与区间 DP
Day 8 模拟赛
A
题意
给定一个长为 \(n\) 的序列 \(a\),把序列里每个数依次加入另一个序列的前面,如果被加入的序列含有加入的数则把该数从原位置调到序列开头。求最后被加入到序列。
题解
签到题。倒序遍历原序列,如果当前值未被标记则输出并标记。代码懒得放了。
B
题意
有 \(n\) 个任务,每天可以完成 \(1\) 或 \(2\) 个,求完成所有任务的方案数。
题解
设 \(f_i\) 为完成 \(i\) 个任务的方案数,那么有转移方程 \(f_i=f_{i-1}+f_{i-2}\),发现就是斐波那契数列。直接算就行了,代码懒得放了。
C
题意
给定一个长 \(n\) 的整数序列 \(a\),在其中选取恰好 \(k\) 个无重叠的非空连续子序列,第 \(i\) 个子序列价值为 \(s_i\times i\)(其中 \(s_i\) 为),求这些子序列的总价值最大值。
题解
类似去年 Day 7 B。
设 \(f_{i,j}\) 表示第 \(i\) 段结尾为 \(j\) 的最大收益,那么有两种情况:
- 不开新段,\(f_{i,j}=f_{i,j-1}+a_j*i\);
- 开新段,\(f_{i,j}=\max_{k=1}^{j-1}f_{i-1,k}\)。
转移取最大即可。但是如果每次转移都要求一遍 \(\max_{k=1}^{j-1}f_{i-1,k}\) 的话时间复杂度是 \(O(kn^2)\) 的,会超时。考虑定义 \(x_{i,j}=\max_{k=1}^{j}f_{i,k}\) 这个可以在转移的时候同时求,最终答案就是 \(x_{k,n}\)。
代码:
#define ll long long
const int _mxn=1e5+5,_mxk=1000+5;
int n,k,a[_mxn];
ll f[_mxk][_mxn],mx[_mxk][_mxn];//f[i][j]:第 i 段结尾为 j 的最大收益;mx[i][j]:f[i][1~j] 的最大值
int main()
{
___();
int _;
cin>>_;
while(_--)
{
cin>>n>>k;
for(int i=1;i<=n;i++)
{
cin>>a[i];
}
for(int i=1;i<=k;i++)
{
for(int j=0;j<=n;j++)
f[i][j]=mx[i][j]=-1e12;
}
for(int i=1;i<=k;i++)
{
for(int j=1;j<=n;j++)
{
f[i][j]=max(f[i][j-1],mx[i-1][j-1])+a[j]*i;
mx[i][j]=max(mx[i][j-1],f[i][j]);
}
}
cout<<mx[k][n]<<endl;
}
return 0;
}
D
题意
题解
显然给出的序列是需要构造的二叉搜索树的中序遍历。那么我们枚举根节点然后判断左右半边行不行就好了。但是直接搜是会 TLE 的,那么我们加个记忆化,定义 \(f_{l,r,x}\) 表示 \([l,r]\) 是否可行,\(x=0\) 表示根为 \(l-1\),否则为 \(r+1\)。
代码:
const int _mxn=700+5;
int n,a[_mxn],gcd[_mxn][_mxn];
bool f[_mxn][_mxn][2],vis[_mxn][_mxn][2];//f[l][r][x] 表示 [l,r] 是否可行,x=0 表示根为 l-1,否则为 r+1
bool dfs(int u,int l,int r,int d)//u 为根,[l,r] 区间能否组成二叉搜索树
{
if(l>r)
return true;
if(vis[l][r][d])
return f[l][r][d];
vis[l][r][d]=true;
for(int i=l;i<=r;i++)
{
if(gcd[u][i]>1)
{
if(dfs(i,l,i-1,0)&&dfs(i,i+1,r,1))
return f[l][r][d]=true;
}
}
return f[l][r][d]=false;
}
int main()
{
___();
int _;
cin>>_;
while(_--)
{
cin>>n;
for(int i=1;i<=n;i++)
{
cin>>a[i];
for(int j=1;j<i;j++)
gcd[i][j]=gcd[j][i]=__gcd(a[i],a[j]);
}
memset(f,false,sizeof(f));
memset(vis,false,sizeof(vis));
bool fans=false;
for(int i=1;i<=n;i++)
{
if(dfs(i,1,i-1,0)&&dfs(i,i+1,n,1))
{
fans=true;
break;
}
}
cout<<(fans?"Yes":"No")<<endl;
}
return 0;
}
E
题意
题解
老师的 std:
#include<bits/stdc++.h>
using namespace std;
#define MAX 100005
#define ll long long
int n;
ll a[MAX][3];
ll f[MAX][3];
ll val(int i,int p,int q){
if(q<p) swap(p,q);
ll ret=0;
for(int o=p;o<=q;o++) ret+=a[i][o];
return ret;
}
ll qz[MAX];
void sol(){
cin>>n;
for(int j=0;j<3;j++)
for(int i=1;i<=n;i++)
cin>>a[i][j];
for(int i=1;i<=n;i++)
qz[i]=qz[i-1]+val(i,0,2);
memset(f,0x80,sizeof(f));
f[0][0]=0;
ll mx0=0,mx2=-1e18;
for(int i=1;i<=n;i++){
for(int p=0;p<3;p++)
for(int q=0;q<3;q++){
f[i][p]=max(f[i][p],
f[i-1][q]+val(i,p,q));
}
f[i][2]=max(f[i][2],mx0+qz[i]);
f[i][0]=max(f[i][0],mx2+qz[i]);
mx0=max(mx0,f[i][0]-qz[i]);
mx2=max(mx2,f[i][2]-qz[i]);
}
cout<<f[n][2]<<endl;
}
int main(){
ios::sync_with_stdio(0);
cin.tie(0);
int t=1;cin>>t;
while(t--) sol();
return 0;
}
Day 9
状压 DP
Day 9 模拟赛
A
题意
给定长为 \(n(1\le n\le1000)\) 的序列 \(a\),求序列中任意两个数差值的绝对值的最小值。
题解
\(O(n^2)\) 暴力枚举,代码不放了。
B
题意
考古学家小可发现了一座神秘法老陵墓,墓中有 \(m\) 个储藏室。每个储藏室 \(i\) 包含 \(a_i\) 个黄金宝箱,每个宝箱内装有 \(b_i\) 枚法老金币。但是因为人手不足,小可的考古队只能携带 \(n\) 个宝箱离开古墓。宝箱不可拆分,只能整箱取走。求最多能获得的金币数。
题解
直接取最大价值的 \(n\) 个宝箱就行了。代码不放了。