这套题可做题挺多的,而且有几个构造和模拟题 一些不可做题和无聊的题就没补了
A - Nasta Rabbara
因为我是dser 赛后唯一补的题 当时比赛的时候没读懂,看没什么人过也放弃了 确实有点麻烦的题 但是很有意思
题意:给出n个点,m条边,q个询问 每个询问给一个区间l,r 问区间l,r内的边是否能构成二分图
一般像这种动态加边删边的,就想到lct了 不过并没想到什么处理区间问题的好方法
看了题解才知道:求出所有的最小的可以构成奇环的区间 看询问的区间是否包含至少一个这样的区间即可
如何去求呢
不停的往集合里面加边:
如果当前未联通 那么直接加入该边
如果加入当前边 构成了偶数环 那么把编号最小的边去掉
如果加入当前边 构成了奇数环 那么我们就得到了一个最小的可构成奇数环的区间 我们放入答案集合 并且删去 该区间左端点之前的所有边(包括左端点) 这样就可以继续去找下一个最小的可构成奇数环的区间
因为偶数环删除可以保证我们找到的构成奇数环的区间是最小的
#include<bits/stdc++.h>
#define lc c[x][0]
#define rc c[x][1]
#define R register int
#define I inline void
#define pa pair<int,int>
using namespace std;
const int N = 4e5+100;
pa p[N];
int n,m,q,tot;
int f[N],c[N][2],val[N],siz[N],mi[N],r[N],st[N],vis[N];
struct edge{
int u,v;
}e[N];
inline int in(){
R w=0,x=0;char c=0;
while(c<'0'||c>'9') w|=c=='-',c=getchar();
while(c>='0'&&c<='9') x=(x<<1)+(x<<3)+(c^48),c=getchar();
return w?-x:x;
}
inline bool nroot(R x){
return c[f[x]][0]==x||c[f[x]][1]==x;
}
I pushr(R x){
swap(lc,rc);r[x]^=1;
}
I pushup(R x){
siz[x]=siz[lc]+siz[rc]+(x>n);
mi[x]=x;
if(lc&&val[mi[lc]]<val[mi[x]]) mi[x]=mi[lc];
if(rc&&val[mi[rc]]<val[mi[x]]) mi[x]=mi[rc];
}
I pushdown(R x){
if(r[x]){
if(lc) pushr(lc);
if(rc) pushr(rc);
r[x]=0;
}
}
I rotate(R x){
R y=f[x],z=f[y],k=c[y][1]==x,w=c[x][k^1];
if(nroot(y)) c[z][c[z][1]==y]=x; c[x][k^1]=y;c[y][k]=w;
if(w) f[w]=y; f[x]=z;f[y]=x;
pushup(y);
}
I splay(R x){
R y=x,z=0;
st[++z]=y;
while(nroot(y)) st[++z]=y=f[y];
while(z) pushdown(st[z--]);
while(nroot(x)){
y=f[x],z=f[y];
if(nroot(y))
rotate((c[y][1]==x)^(c[z][1]==y)?x:y);
rotate(x);
}
pushup(x);
}
I access(R x){
for(R y=0;x;x=f[y=x])
splay(x),rc=y,pushup(x);
}
I makeroot(R x){
access(x);splay(x);
pushr(x);
}
I split(R x,R y){
makeroot(x);
access(y);splay(y);
}
inline int findroot(R x){
access(x);splay(x);
while(lc) pushdown(x),x=lc;
splay(x);
return x;
}
inline void link(int x,int y){
makeroot(x);
f[x]=y;
}
inline void cut(int x,int y){
split(x,y);
f[x]=c[y][0]=0;
pushup(y);//少了个儿子,也要上传一下
}
inline bool judge(R x,R y){
makeroot(x);
if(findroot(y)==x) return true;
return false;
}
I clear(int st,int ed){
for(int i = st; i <= ed; i++)
if(vis[i])
cut(i+n,e[i].u),cut(i+n,e[i].v),vis[i]=0;
}
int main(){
n=in(),m=in(),q=in();
for(int i = 0; i <= n; i++) val[i]=2e9,mi[i]=i;
for(int i = 1; i <= m; i++){
mi[i+n]=i+n;
val[i+n]=i+n;
e[i].u=in(),e[i].v=in();
}
int st=1;
for(int i = 1; i <= m; i++){
R u=e[i].u,v=e[i].v;
if(judge(u,v)){
split(u,v);
int c = mi[v];
if(siz[v]&1){
cut(c,e[c-n].u);cut(c,e[c-n].v);
vis[c-n]=0;
}else{
++tot;
p[tot].first=c-n;p[tot].second=i;
clear(st,c-n);
st=c-n+1;
}
link(u,i+n);link(v,i+n);
vis[i]=1;
}else{
link(u,i+n);link(v,i+n);
vis[i]=1;
}
}
for(int i = 1; i <= q; i++){
R l,r;
l=in(),r=in();
int pos = lower_bound(p+1,p+1+tot,make_pair(l,0))-p;
if(pos==tot+1||r<p[pos].second) puts("Possible");
else puts("Impossible");
}
return 0;
}
B - Colored Blankets
C - Component Tree
D - Data Center
题意:首先内存大于等于m 并且数量最少 其次1最多
思路:队友给的 因为数量要最少 我们可以先确定这个数量 肯定是把内存降序排 然后选前几个总和能超出m内存的构成答案集合 确定了数量以后 把剩下的分成01两部分 每次从答案集合中 选出内存最少的并且标号是0的 交换后保证内存大于等于m 向答案里面加入内存最大的并且标号为1的 如果不行则结束
#include<stdio.h>
#include<algorithm>
using namespace std;
#define ll long long
struct node{
ll a,b,w;
};
node x[200010];
node y[200010],z[200010];
bool cmp(node i,node j){
return i.a>j.a;
}
int ans[200010];
int main(){
ll n,m;
scanf("%lld %lld",&n,&m);
int cnty=0,cntz=0;
for(int i=1;i<=n;i++){
ll tempa,tempb;
scanf("%lld %lld",&tempa,&tempb);
node temp;
temp.a=tempa;
temp.b=tempb;
temp.w=i;
x[i]=temp;
if(tempb==0){
y[++cnty]=temp;
}
else{
z[++cntz]=temp;
}
}
sort(x+1,x+n+1,cmp);
sort(y+1,y+cnty+1,cmp);
sort(z+1,z+cntz+1,cmp);
int num=0;
ll temp=0;
for(int i=1;i<=n;i++){
num++;
temp=temp+x[i].a;
if(temp>=m){
break;
}
}
ll now=0;
int wz,wy;
if(cntz<=num){
for(int i=1;i<=cntz;i++){
now=now+z[i].a;
}
for(int i=1;i<=num-cntz;i++){
now=now+y[i].a;
}
wz=cntz;
wy=num-cntz;
}
else{
for(int i=1;i<=num;i++){
now=now+z[i].a;
}
wz=num;
wy=0;
}
while(now<m){
wz--;
wy++;
now=now-z[wz+1].a+y[wy].a;
}
printf("%d %d\n",num,wz);
for(int i=1;i<=wz;i++){
printf("%d",z[i].w);
if(i==wz&&wz==num){
printf("\n");
}
else{
printf(" ");
}
}
for(int i=1;i<=wy;i++){
printf("%d",y[i].w);
if(i==wy){
printf("\n");
}
else{
printf(" ");
}
}
}
E - Election of a Mayor
题意好像是让第一个人的得票数大于等于一半 并且可以合并两个站的票 (合并后不能再合并) 问要合并哪些站
队友想的 我全程划水
#include<stdio.h>
int m[200010],r[200010];
int ansl[200010],ansr[200010];
int main(){
int n;
scanf("%d",&n);
int s=0,h=0;
for(int i=1;i<=n;i++){
scanf("%d %d",&m[i],&r[i]);
if(m[i]>r[i])s++;
}
for(int i=1;i<n;i++){
if(2*s>n-h){
break;
}
if(m[i]<=r[i]&&m[i+1]<=r[i+1]){
h++;
ansl[h]=i;
ansr[h]=i+1;
i++;
}
else if(m[i]+m[i+1]>r[i]+r[i+1]){
if(m[i]<=r[i]&&m[i+1]>=r[i+1]){
h++;
ansl[h]=i;
ansr[h]=i+1;
i++;
}
else if(m[i]>=r[i]&&m[i+1]<=r[i+1]){
h++;
ansl[h]=i;
ansr[h]=i+1;
i++;
}
}
}
if(2*s>n-h){
printf("%d\n",h);
for(int i=1;i<=h;i++){
printf("%d %d\n",ansl[i],ansr[i]);
}
}
else{
printf("-1");
}
}
F - Ilya Muromets
题意:选两段长度为k的子串 让它们的和最大 (选完一段以后 另一段可以相前并)
其实就是选两段长度为k的连续区间 让它们的和最大 能不能并无所谓 反正也可以拆成两端长度为k的连续子串
那么我们通过前缀和已经前缀最大值 选两端 让它们的和最大就行
#include<stdio.h>
#include<algorithm>
using namespace std;
#define maxn 200010
int f[maxn];
int sum[maxn];
int main(){
int n,k;
scanf("%d %d",&n,&k);
for(int i=1;i<=n;i++){
scanf("%d",&f[i]);
f[i]=f[i]+f[i-1];
if(i>=k){
sum[i]=f[i]-f[i-k];
}
}
if(2*k>=n){
printf("%d\n",f[n]);
return 0;
}
int ans=0;
int maxz=sum[k];
for(int i=2*k;i<=n;i++){
if(sum[i]+maxz>ans){
ans=sum[i]+maxz;
}
maxz=max(maxz,sum[i-k+1]);
}
printf("%d\n",ans);
}
G - FacePalm Accounting
这题队友提供了一个思路然后我把它秒了
题意:让改变的总和最小 然后使得所有长度为k的子串和都小于0 并且每个值不能小于序列的最小值(保证最小值为负数)
思路:我们从第一个长度为k的子串开始(1~k) 显然 如果这一段和不小0 我们改变的位置越靠后越好 然后再一次往后挪动一个位置 显然对于等于最小值得位置 我们不再更改 我们可以建立一个栈 一开始存1到k-1可更改的位置 然后当一个位置更改到最小值以后就把它弹出栈 当我们遍历到一个新的位置 就把它压入栈 这样就可以保证更新的位置尽量靠后了 因为答案一定存在 所以对于与当前位置大于k的位置 我们不需要弹出(如果不能保证有答案 我们需要弹出前面一些更改却没有效益的位置) 用树状数组去求和和更新即可
#include<bits/stdc++.h>
using namespace std;
const int N = 2e5+100;
typedef long long ll;
ll c[N];
int n,k;
void add(int x,ll val){
while(x<=n){
c[x]+=val;
x+=x&-x;
}
}
ll query(int x){
ll ret = 0;
while(x){
ret+=c[x];
x-=x&-x;
}
return ret;
}
ll a[N];
int st[N],tp;
int main(){
scanf("%d%d",&n,&k);
ll mi = 2e9;
for(int i = 1; i <= n; i++) scanf("%lld",&a[i]),mi=min(a[i],mi),add(i,a[i]);
for(int i = 1; i <= k-1; i++)
if(a[i]!=mi) st[++tp]=i;
ll ans = 0;
for(int i = k; i <= n; i++){
if(a[i]!=mi) st[++tp]=i;
ll q;
while((q=(query(i)-query(i-k)))>=0){
//printf("tp=%d\n",tp);
ll c = max(-q-1,mi-a[st[tp]]);
//printf("q=%lld c=%lld\n",q,c);
add(st[tp],max(-q-1,mi-a[st[tp]]));
ans+=-(max(-q-1,mi-a[st[tp]]));
a[st[tp]]+=max(-q-1,mi-a[st[tp]]);
if(-q-1<=mi-a[i]) tp--;
}
}
printf("%lld\n",ans);
for(int i = 1; i <= n; i++)
printf("%lld ",a[i]);
return 0;
}
H - Minimal Agapov Code
I - Sale in GameStore
水题就不说了
#include<stdio.h>
#include<algorithm>
using namespace std;
int p[2010];
int main(){
int n;
scanf("%d",&n);
for(int i=1;i<=n;i++){
scanf("%d",&p[i]);
}
sort(p+1,p+n+1);
int ans=1;
int now=0;
for(int i=1;i<n;i++){
if(now+p[i]<=p[n]){
ans++;
now=now+p[i];
}
else{
break;
}
}
printf("%d\n",ans);
}
J - Getting Ready for VIPC
K - Treeland
题意:一颗树有n个结点 每个点按距离不递减的顺序给出一个排列 求树的结构
思路:构造题 以前还没做过这种构造一颗树的形态的 本来想了好久 刚想放弃的时候 突然想出了一个做法 我选取1为根结点 距离它最远的一定是叶子结点 然后我再找这个叶子结点的序列里面 离它最近的 这肯定是和这个叶子相连的边 放入答案集合 并且标记这条边
然后我们再重复找叶子 并且删边的(删边就是计入答案集合并且标记 让新的叶子出来)
#include<bits/stdc++.h>
using namespace std;
#define pa pair<int,int>
vector<pa>ans;
map<pa,int>mp;
const int N = 2003;
int dis[N][N];
int main(){
int t;
scanf("%d",&t);
while(t--){
ans.clear();
mp.clear();
int n;
scanf("%d",&n);
for(int i = 1; i <= n; i++){
for(int j = 1; j <= n; j++)
scanf("%d",&dis[i][j]);
}
for(int i = 1; i <= n-1; i++){
int ye = dis[1][n-i+1];
for(int j = 2; j <= n; j++){
if(mp[make_pair(dis[ye][j],ye)]==0){
ans.push_back(make_pair(dis[ye][j],ye));
mp[make_pair(dis[ye][j],ye)]=1;
mp[make_pair(ye,dis[ye][j])]=1;
break;
}
}
}
for(auto v:ans){
printf("%d %d\n",v.first,v.second);
}
puts("");
}
return 0;
}
L - Useful Roads
M - Variable Shadowing
模拟题 不想多说 大力出奇迹
#include<bits/stdc++.h>
using namespace std;
int pos[2550][2];
char s[55][55];
int len[55];
char t[2550];
int match[2550],st[2550],tp;
int alst[29][2550],altp[29];
int main(){
int n;
scanf("%d",&n);
int now = 0;
getchar();
for(int i = 1; i <= n; i++){
gets(s[i]+1);
len[i]=strlen(s[i]+1);
for(int j = 1; j <= len[i]; j++){
t[j+now]=s[i][j];
//printf("i=%d j=%d j+now=%d\n",i,j,j+now);
pos[j+now][0]=i;
pos[j+now][1]=j;
}
now+=len[i];
}
//printf("%s\n",t+1);
int lent=strlen(t+1);
for(int i = 1; i <= lent; i++){
if(t[i]=='{') st[++tp]=i;
else if(t[i]=='}'){
match[i]=st[tp];
tp--;
}
}
tp=0;
for(int i = 1; i <= lent; i++){
if(t[i]=='{'){
for(int j = 0; j <= 25; j++)
alst[j][++altp[j]]=i;
}
else if(t[i]=='}'){
for(int j = 0; j <= 25; j++){
while(alst[j][altp[j]]!=match[i])
altp[j]--;
altp[j]--;
}
}else if(t[i]==' ') continue;
else{
int c = t[i]-'a',laspos=0;
for(int k = altp[c]; k >= 1; k--)
if(t[alst[c][k]]==t[i]){
laspos=alst[c][k];
break;
}
if(laspos){
printf("%d:%d: warning: shadowed declaration of %c, the shadowed position is %d:%d\n",pos[i][0],pos[i][1],t[i],pos[laspos][0],pos[laspos][1]);
}
alst[c][++altp[c]]=i;
}
}
return 0;
}