牛客周赛Round62
G-小红的数轴移动(二)_牛客周赛 Round 62 (nowcoder.com)
思路:建图,不妨把数轴往右移动100个单位,那么数轴变为[0,200],源点变为100。对于每一天操作ai,在数轴[0,100)建边i+ai,在数轴(100,200]建边i-ai。然后直接跑最短路。因为题目要把选择操作的编号依次输出出来,所以需要road[i]表示点i的前驱节点,还不够,因为要知道操作的编号。那么还需要roadId[i]表示点i是由哪个编号的操作到达的。并且还需要viss0表示在当前路径中,哪些操作已经被使用过了,避免一个操作在同一个路径中重复使用。之后还原路径,输出操作Id即可。要注意的是,不一定每次都能到达0点而停下来,所以还要判断不能到达0点的情况,一开始写着写着把这个给忽略了,默认以为保证能到达0点。
typedef struct info{
int w,nod;
vector<bool> viss;
bool operator < (const info &x) const{
return w>x.w; 优先队列中,小的在前
}
}info;
int n,x;
int arr[105];
vector<array<int,3>> vct[205];
priority_queue<info> pq;
int dis[205],road[205],roadId[205];
bool vis[205];
void dijkstra(int s){
for(int i=0;i<=200;i++) dis[i]=INT_MAX,vis[i]=0;
dis[s]=0;
vector<bool> viss(205,0);
pq.emplace(info{0,s,viss});
while(pq.size()){
int from=pq.top().nod;
vector<bool> viss0=pq.top().viss;
pq.pop();
if(vis[from]) continue;
vis[from]=1;
for(auto v:vct[from]){
int to=v[0],w=v[1],id=v[2];
if(dis[to]>dis[from]+w&&!viss0[id]){
road[to]=from,roadId[to]=id;
dis[to]=dis[from]+w;
key:要标记当前id,是因为到了当前这个点,是不知道之前用过哪些id的操作的,为了避免重复使用,这里需要viss0
viss0[id]=1;
pq.emplace(info{dis[to],to,viss0});
viss0[id]=0; //细节.同一个from,走向下一个点,自会标记当前的id.这里需要回溯一下。
}
}
}
}
因为dijkstra的边权不能为负值,那么全部值偏移100. 那么0点变为100.
https://ac.nowcoder.com/acm/contest/91177/G
void solve(){ //G 建图
cin>>n>>x;
x+=100;
int sum=0;
for(int i=1;i<=n;i++){
cin>>arr[i];
sum+=arr[i];
int step=arr[i];
对于每一次操作,在每一个点都建边.
for(int j=0;j<100;j++) vct[j].emplace_back(array<int,3>{j+step,step,i});
for(int j=101;j<=200;j++) vct[j].emplace_back(array<int,3>{j-step,step,i});
}
dijkstra(x);
bool mark[205]={0};
int cur=100;
vector<int> ans;
while(cur!=x&&cur!=0){ 还原路径
ans.emplace_back(roadId[cur]);
mark[roadId[100]]=1;
cur=road[cur];
}
reverse(ans.begin(),ans.end());
对于到达不了0点的情况,直接输出sum即可--写着写着把题目以为是到达0点的最短距离,误会题意了,以为一定可以到达0点是有解的.
if(dis[100]==INT_MAX){
cout<<sum<<endl;
for(int i=1;i<=n;i++) cout<<i<<" ";
}
else{
cout<<dis[100]<<endl;
for(auto a:ans){
cout<<a<<" ";
mark[a]=1;
}
for(int i=1;i<=n;i++) if(!mark[i]) cout<<i<<" ";
}
}
E-小红的中位数查询(easy)_牛客周赛 Round 62 (nowcoder.com)
思路:莫队的板子.但是hard冲不过去,会TLE。得需要主席树。
int n,m,B;
int arr[100005],p[100005];
typedef struct Q{
int l,r,idx;
}Q;
Q q[100005];
//bool cmp(Q a,Q b){ 普通分块排序
// if(a.l/B!=b.l/B) return a.l<b.l;
// return a.r<b.r;
//}
bool cmp(Q a,Q b){ 玄学奇偶性排序
if (p[a.l] ^ p[b.l]) return a.l/B < b.l/B ;
else{
if (p[a.l] & 1) return a.r < b.r;
else return a.r > b.r;
}
}
int l=0,r=0,ans[100005];
P2709 小B的询问
https://www.luogu.com.cn/problem/P2709
void solve(){ //E
cin>>n>>m;
B=sqrt(n);
for(int i=1;i<=n;i++) cin>>arr[i];
for(int i=1;i<=m;i++) cin>>q[i].l>>q[i].r,q[i].idx=i,p[q[i].l]=q[i].l/B,p[q[i].r]=q[i].r/B;
sort(q+1,q+m+1,cmp);
l=q[1].l+1,r=q[1].l;
multiset<int> smal,big;
for(int i=1;i<=m;i++){
while(l>q[i].l){ //扩张
--l;
if(smal.size()==0||arr[l]>=*smal.begin()) smal.emplace(arr[l]);
else big.emplace(arr[l]);
while(smal.size()<=big.size()) smal.emplace(*big.rbegin()),big.erase(prev(big.end()));
while(smal.size()-big.size()>1) big.emplace(*smal.begin()),smal.erase(smal.begin());
}
while(r<q[i].r){ //扩张
++r;
if(smal.size()==0||arr[r]>=*smal.begin()) smal.emplace(arr[r]);
else big.emplace(arr[r]);
while(smal.size()<=big.size()) smal.emplace(*big.rbegin()),big.erase(prev(big.end()));
while(smal.size()-big.size()>1) big.emplace(*smal.begin()),smal.erase(smal.begin());
}
while(l<q[i].l){ //收缩
auto p0=smal.find(arr[l]);
if(p0!=smal.end()) smal.erase(p0);
else big.erase(big.find(arr[l]));
while(smal.size()<=big.size()) smal.emplace(*big.rbegin()),big.erase(prev(big.end()));
while(smal.size()-big.size()>1) big.emplace(*smal.begin()),smal.erase(smal.begin());
l++;
}
while(r>q[i].r){ //收缩
auto p0=smal.find(arr[r]);
if(p0!=smal.end()) smal.erase(p0);
else big.erase(big.find(arr[r]));
while(smal.size()<=big.size()) smal.emplace(*big.rbegin()),big.erase(prev(big.end()));
while(smal.size()-big.size()>1) big.emplace(*smal.begin()),smal.erase(smal.begin());
r--;
}
ans[q[i].idx]=*smal.begin();
}
for(int i=1;i<=m;i++) cout<<ans[i]<<endl;
}
D-小红的树上移动_牛客周赛 Round 62 (nowcoder.com)
思路:最最简单的期望题,只是dfs到叶子节点,然后计算答案。
const int mod=1e9+7;
int n;
vector<int> vct[100005];
int quickpow(int a,int b){
int res=1;
while(b){
if(b&1) res*=a,res%=mod;
a*=a,a%=mod;
b>>=1;
}
return res;
}
int ans=0;
void dfs(int from,int fa,int fm,int cnt){
if(vct[from].size()==1&&from!=1){
ans+=cnt*quickpow(fm,mod-2)%mod,ans%=mod;
return;
}
for(auto v:vct[from]) {
if(v!=fa){
int cc=vct[from].size();
if(fa!=0) cc--;
dfs(v,from,fm*cc%mod,cnt+1); 分母q可以在计算过程中直接取mod,最后计算p*(q^-1)即可.
}
}
}
void solve(){ //D 最简单的期望题..其实就是一个dfs.
cin>>n;
for(int i=1;i<=n-1;i++){
int u,v; cin>>u>>v;
vct[u].emplace_back(v);
vct[v].emplace_back(u);
}
dfs(1,0,1,1);
cout<<ans;
}
牛客周赛Round32
E-小苯的等比数列_牛客周赛 Round 38 (nowcoder.com)
思路:实际上就是暴力枚举每一个数字作为起点,再枚举公比。但是得用vis和q*arr[i]<=m剪枝,否则就TLE.
int n;
int arr[200005];
int mark[200005];
int vis[200005];
void solve(){ //牛客周赛Round38 //E
cin>>n;
int ans=0,mx=0;
for(int i=1;i<=n;i++){
cin>>arr[i];
mark[arr[i]]++,mx=max(mx,arr[i]);
ans=max(ans,mark[arr[i]]); //q==1
}
for(int i=1;i<=n;i++){
if(vis[arr[i]]) continue;
vis[arr[i]]=1; //这个也不能缺,缺了就TLE
for(int q=2;q*arr[i]<=mx;q++){ //枚举公比 //q*arr[i]<=mx 加了这个判断只跑51ms
int cnt=0;
for(int j=arr[i];j<=mx;j*=q){
if(mark[j]) cnt++;
else ans=max(ans,cnt),cnt=0;
}
ans=max(ans,cnt);
}
}
cout<<ans;
}
F-小苯的回文询问_牛客周赛 Round 38 (nowcoder.com)
思路:又是莫队模板,处理扩展和收缩只要3个变量即可。写麻烦了可能会TLE。
int n,m,B;
int arr[100005],p[200005];
typedef struct Q{
int l,r,idx;
}Q;
Q q[200005];
bool cmp(Q a,Q b){ 玄学奇偶性排序
if (p[a.l] ^ p[b.l]) return a.l/B < b.l/B ;
else{
if (p[a.l] & 1) return a.r < b.r;
else return a.r > b.r;
}
}
int l=0,r=0,ans[200005];
void solve(){ //牛客周赛Round38 //F 又是莫队
cin>>n>>m;
B=sqrt(n);
unordered_map<int,int> ha;
int num=0;
for(int i=1;i<=n;i++) {
cin>>arr[i];
if(ha[arr[i]]==0) ha[arr[i]]=++num; 离散化
arr[i]=ha[arr[i]];
}
for(int i=1;i<=m;i++) cin>>q[i].l>>q[i].r,q[i].idx=i,p[q[i].l]=q[i].l/B,p[q[i].r]=q[i].r/B;
sort(q+1,q+m+1,cmp);
l=q[1].l+1,r=q[1].l;
int cnt[200005];
int cntNex=0,cnt2=0,cnt3=0;
for(int i=1;i<=m;i++){
while(l>q[i].l){ //扩张
--l;
cnt[arr[l]]++;
if(cnt[arr[l]]==2) cnt2++;
if(cnt[arr[l]]==3) cnt3++;
if(arr[l]==arr[l+1]) cntNex++;
}
while(r<q[i].r){ //扩张
++r;
cnt[arr[r]]++;
if(cnt[arr[r]]==2) cnt2++;
if(cnt[arr[r]]==3) cnt3++;
if(arr[r]==arr[r-1]) cntNex++;
}
while(l<q[i].l){ //收缩
cnt[arr[l]]--;
if(cnt[arr[l]]==1) cnt2--;
if(cnt[arr[l]]==2) cnt3--;
if(arr[l]==arr[l+1]) cntNex--;
l++;
}
while(r>q[i].r){ //收缩
cnt[arr[r]]--;
if(cnt[arr[r]]==1) cnt2--;
if(cnt[arr[r]]==2) cnt3--;
if(arr[r]==arr[r-1]) cntNex--;
r--;
}
if(cnt3||cnt2>cntNex) ans[q[i].idx]=1;
}
for(int i=1;i<=m;i++) ans[i]?cout<<"YES"<<endl:cout<<"NO"<<endl;
}
插入一题2021牛客暑假训练营3的签到容斥题:
J-Counting Triangles_2024牛客国庆集训派对day1 (nowcoder.com)
题意:给定一个完全图,每一条边都为黑或白色,问三元组(a,b,c),a<b<c且边(a,b),(b,c),(a,c)的颜色一样的个数.
思路:一开始还想着怎么dfs,或者枚举中间点然后左边右边同样颜色的个数相乘,但是这样是不知道a,c点是否连着相同的颜色,如果再枚举的话,复杂度是o(n^3)的。正解是容斥:因为是完全图,那么所有三元组的个数为C(3,n)。不符合条件的个数是多少?画图可以发现,在一个不满足条件的三元组的三条边种,必然两条边是颜色是一样的,一条是不一样的。并且必然有两个点连着黑白边,只有一个点连着同样颜色的边。那么想要筛去一个三元组只要计算每一个点 黑边个数*白边个数即可。又因为一个三元组中会有两个连着黑边边的点,意味着一个三元组会被筛去两次,那么总各个数除2即可。答案即为C(3,n)-cntt/2。
namespace GenHelper
{
unsigned z1,z2,z3,z4,b,u;
unsigned get()
{
b=((z1<<6)^z1)>>13;
z1=((z1&4294967294U)<<18)^b;
b=((z2<<2)^z2)>>27;
z2=((z2&4294967288U)<<2)^b;
b=((z3<<13)^z3)>>21;
z3=((z3&4294967280U)<<7)^b;
b=((z4<<3)^z4)>>12;
z4=((z4&4294967168U)<<13)^b;
return (z1^z2^z3^z4);
}
bool read() {
while (!u) u = get();
bool res = u & 1;
u >>= 1; return res;
}
void srand(int x)
{
z1=x;
z2=(~x)^0x233333333U;
z3=x^0x1234598766U;
z4=(~x)+51;
u = 0;
}
}
using namespace GenHelper;
int cntW[8005];
int cntB[8005];
看了题解之后,恍然大悟,是容斥..
总的答案:Cn3
减去不合法的三元组:枚举每一个点i,cntW[i]*cntB[i]就是不合法的三元组.
结果要除二,因为每一个不合法的三元组都被筛了两次.
不用考虑其中的三个数字的大小,因为无论数字的大小关系如何,总是可以存在a<b<c的,并且因为存在白,黑边,那么这个三元组就是不合法的.
https://ac.nowcoder.com/acm/contest/90188/J
void solve() { G签到..
int n, seed;
cin >> n >> seed;
srand(seed);
for (int i = 1; i <= n; i++){
for (int j = i + 1; j <= n; j++){
read()?cntB[i]++,cntB[j]++:(cntW[i]++,cntW[j]++); //三目运算后面的逗号要加括号!!
}
}
if(n<3) cout<<"0";
else{
int ans=n*(n-1)*(n-2)/(3*2*1);
int cntt=0;
for(int i=1;i<=n;i++) cntt+=cntW[i]*cntB[i];
cout<<ans-cntt/2;
}
}