Problem A
- 思路
这是一道带权并查集问题
因为只有三种种类,我们分别用0,1,2来表示,即0-->1,1-->2,2-->0。
我们需要知道x和y的关系,即需知道x和祖先xx的关系,y和祖先yy的关系,即可得x和y的关系。
①当x的祖先和y的祖先不同时,可知x和y的关系在前面并没有体现,所以此话一定是真的
②当x的祖先和y的祖先相同时,通过x,y和祖先的关系可知x所在种类和y所在种类的关系,即可判断话的真假
- AC代码
#include <iostream>
#include <cstdio>
using namespace std;
#define ll long long
ll n,k,p,x,y,ans;
ll f[50005],val[50005];
int found(ll x)
{
if(x==f[x])return x;
ll temp=found(f[x]);
val[x]=(val[x]+val[f[x]])%3;
return f[x]=temp;
}
int fun(ll d,ll x,ll y)
{
ll xx=found(x);
ll yy=found(y);
if(xx!=yy)
{
f[yy]=xx;
val[yy]=(val[x]-val[y]+d+3)%3;
return 0;
}
else
{
if((val[y]-val[x]+3)%3==d)return 0;
else return 1;
}
}
int main()
{
scanf("%lld%lld",&n,&k);
for(int i=1;i<=n;i++)f[i]=i;
while(k--)
{
scanf("%lld%lld%lld",&p,&x,&y);
if(x>n||y>n||(p==2&&x==y)){++ans;continue;}
if(fun(p-1,x,y))++ans;
}
printf("%lld
",ans);
return 0;
}
Problem B
- 思路
此题是一个思维题,里面有用到线段树的知识
因为是从1开始变到p,所以数组中必然要有p出现。
其中所有的0都要变为非0且小于等于q的数字,我们只需要把0变为左边相邻的数字即可,注意第一个数字为0时的情况
然后记录所有数字出现的最小和最大位置,即可得到一个区域,在此区域若出现比其小的数字,则不可能。
- AC代码
#include <iostream>
#include <cstdio>
using namespace std;
#define ll long long
const int INF=0x3f3f3f3f;
int n,q,a[200005],st[1000000],g=0,b[200005][2];
void build(int root,int l,int r)
{
if(l==r)st[root]=a[l];
else
{
int mid=(l+r)>>1;
build(root<<1,l,mid);
build((root<<1)+1,mid+1,r);
st[root]=min(st[root<<1],st[(root<<1)+1]);
}
}
int query(int root,int l,int r,int s,int e)
{
if(l>e||r<s)return INF;
if(s<=l&&e>=r)return st[root];
int mid=(l+r)>>1;
return min(query(root<<1,l,mid,s,e),query((root<<1)+1,mid+1,r,s,e));
}
int main()
{
ios_base::sync_with_stdio(0);
cin.tie(0);
cin>>n>>q;
for(int i=1;i<=n;i++)cin>>a[i];
//判断是否有最大的数字
for(int i=1;i<=n;i++)
if(a[i]==q){g=1;break;}
//将所有的0变成非0数字,保证数组里要有最大的数字
int l=0;
if(!g)
{
for(l=l+1;l<=n;l++)
if(!a[l]){a[l]=q;break;}
if(l>n){cout<<"NO"<<endl;return 0;}
}
else if(!a[1])
{
for(l=2;l<=n;l++)
if(a[l])break;
for(int i=1;i<l;i++)
a[i]=a[l];
}
for(;l<=n;l++)if(!a[l])a[l]=a[l-1];
//记录数字出现的最小的位置和最大的位置
for(int i=1;i<=n;i++)
{
if(!b[a[i]][0])b[a[i]][0]=i;
b[a[i]][1]=i;
}
build(1,1,n);
//判断此数字出现的区间里是否有比它小的数字出现
for(int i=1;i<=q;i++)
{
if(!b[i][0])continue;
if(query(1,1,n,b[i][0],b[i][1])<i)
{
cout<<"NO"<<endl;
return 0;
}
}
cout<<"YES"<<endl;
for(int i=1;i<=n;i++)cout<<a[i]<<" ";
cout<<endl;
return 0;
}
Problem C
- 思路
这题最难绕的就是“左子树+右子树”并不等于总的,因为它update的时候右子树是特殊计算的。还是因为在这个最长上升序列的问题中虽然计算单个区间的时候是不考虑左右影响的,但是update的时候为了正确得到这个区间的结果,右子树还是要考虑左子树的影响的,这是造成val[i<<1]+val[(i<<1)+1]≠≠val[i]的原因。
- AC代码
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
int n,m,val[500010];
double Max[500010];
bool dlt[500010];
int divide(int i,int l,int r,double v){
if (l==r) return Max[i]>v;
int mid=(l+r)>>1;
if (Max[i]<=v) return 0;
if (Max[i<<1]<=v) return divide((i<<1)+1,mid+1,r,v);
else return val[i]-val[i<<1]+divide(i<<1,l,mid,v);
}
void change(int i,int l,int r,int x,double v){
if (l==r){
Max[i]=v;val[i]=1;
return;
}
int mid=(l+r)>>1;
if (x<=mid) change(i<<1,l,mid,x,v);
else change((i<<1)+1,mid+1,r,x,v);
Max[i]=max(Max[i<<1],Max[(i<<1)+1]);
val[i]=val[i<<1]+divide((i<<1)+1,mid+1,r,Max[i<<1]);
}
int main()
{
scanf("%d%d",&n,&m);
for (int i=1;i<=m;i++){
int x,y;scanf("%d%d",&x,&y);
double v=(double)y/(double)x;
change(1,1,n,x,v);
printf("%d
",val[1]);
}
return 0;
}
Problem D
- 思路
dp+最短路
令f[i]为到第i天的最少成本,那么显然有f[i]=min(f[i],f[j]+cost(j+1,i)+k),其中cost(i,j)为i天到j天的最小花费,然后直接最短路搞
- AC代码
#include<bits/stdc++.h>
using namespace std;
const int maxn = 205;
vector<pair<int,int> >e[maxn];
int d[maxn];
int vis[maxn];
int unreach[maxn][maxn];
int f[maxn];
int n,m,k,ee;
int dijkstra(int s,int t)
{
memset(vis,0,sizeof(vis));
for(int i = s;i<=t;i++)
for(int j = 1;j<=m;j++)
{
if(unreach[j][i])
vis[j]=1;
}
for(int i = 1;i<=m;i++)
d[i]=9999999;
queue<int>q;
d[1]=0;
q.push(1);
// vis[1]=1;
while(!q.empty())
{
int u = q.front();
q.pop();
for(int i = 0;i<e[u].size();i++)
{
int v= e[u][i].first;
if(vis[v])
continue;
if(d[v]>d[u]+e[u][i].second)
{
d[v]=d[u]+e[u][i].second;
//vis[v]=1;
q.push(v);
}
}
}
return d[m]*(t-s+1);
}
int main()
{
scanf("%d%d%d%d",&n,&m,&k,&ee);
for(int i = 0;i<ee;i++)
{
int u,v,w;
scanf("%d%d%d",&u,&v,&w);
e[u].push_back(make_pair(v,w));
e[v].push_back(make_pair(u,w));
}
int q;
scanf("%d",&q);
while(q--)
{
int u,a,b;
scanf("%d%d%d",&u,&a,&b);
for(int i = a;i<=b;i++)
unreach[u][i]=1;
}
for(int i = 1;i<=n;i++)
{
f[i]=dijkstra(1,i);
for(int j = 2;j<i;j++)
{
f[i]=min(f[i],f[j]+dijkstra(j+1,i)+k);
}
}
printf("%d
",f[n]);
}
Problem E
此题来自某位学长的友情赞助,是给你们的签到题
- 思路
先把数组a全部赋值为-1,表示数组的这个数未分组。然后一个个数字扫进来,如果这个数字没有分组的话,我们找到这个组的范围max(0,p-k+1)~p,如果前面的最小数字未分组或者分组的情况使它自己的话,我们就从前面最小的数字到p分组为最小的数字。
- AC代码
#include <iostream>
#include <cstdio>
using namespace std;
#define ll long long
const int INF=0x3f3f3f3f;
int main()
{
int a[257],n,k;
for (int i = 0; i < 257; i++) a[i] = -1;
scanf("%d %d", &n, &k);
for (int i = 0; i < n; i++)
{
int p;
scanf("%d", &p);
if (a[p] == -1)
{
for (int j = max(0, p - k + 1); j <= p; j++)
{
if (a[j] == -1 || a[j] == j)
{
for (int k = j; k <= p; k++)
a[k] = j;
break;
}
}
}
printf("%d", a[p]);
if (i != n - 1) printf(" ");
else printf("
");
}
return 0;
}
Problem F
- 思路
二分一下边权。然后跑最小生成树判断即可。
- AC代码
#include<iostream>
#include<cstdio>
#define N 10010
using namespace std;
struct use{int st,en,v1,v2;}e[N*3];
int n,ans,cnt,l,r,m,k,x,y,v,w,fa[N];
int find(int x){if (x!=fa[x]) fa[x]=find(fa[x]);return fa[x];}
bool check(int x){
int temp(0);
for (int i=1;i<=n;i++) fa[i]=i;
for (int i=1;i<=cnt;i++)
if (e[i].v1<=x){
int r1=find(e[i].st),r2=find(e[i].en);
if (r1!=r2){fa[r1]=r2;temp++;}
}
if (temp<k) return false;
for (int i=1;i<=cnt;i++)
if (e[i].v2<=x){
int r1=find(e[i].st),r2=find(e[i].en);
if (r1!=r2){fa[r1]=r2;temp++;}
}
if (temp!=n-1) return false;
return true;
}
int main(){
scanf("%d%d%d",&n,&k,&m);
for (int i=1;i<=m;i++){
scanf("%d%d%d%d",&x,&y,&v,&w);
e[++cnt].st=x;e[cnt].en=y;e[cnt].v1=v;e[cnt].v2=w;
}
l=1;r=30000;
while(l<=r){
int mid=(l+r)>>1;
if (check(mid)) r=mid-1;
else l=mid+1;
}
cout<<l<<endl;
}