正题
Day1
前一天晚上说不着,看了半天发现枕头太高了。
第二天早上一起来就洗了一个澡,神志清醒,跟好舍友Braz出去找早餐吃,结果找了半天麦当劳发现在门口????
来到考场,准备好一切东西进入考场。
第一题:积木大赛?
原题,就是有n个数,ai,每次可以操作一个区间,使得这个区间的数减一,并且这个区间必须大于0。问最少操作多少次?
明显是一个Dp,但是赛场上面我使用10000个vector来存每一个数的位置。然后根据每一次剩下的块数来加、减。
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<iostream>
#include<vector>
using namespace std;
bool tf[100010];
int n;
vector<int> f[10010];
int main(){
scanf("%d",&n);
int x;
int mmax=0;
for(int i=1;i<=n;i++){
scanf("%d",&x);
f[x].push_back(i);
mmax=max(mmax,x);
}
tf[0]=tf[n+1]=true;
int tot=1;
int ans=0;
for(int i=0;i<(int)f[0].size();i++){
if(tf[f[0][i]-1] && tf[f[0][i]+1]) tot--;
else if(!tf[f[0][i]-1] && !tf[f[0][i]+1]) tot++;
tf[f[0][i]]=true;
}
for(int i=1;i<=mmax;i++){
ans+=tot;
for(int j=0;j<(int)f[i].size();j++){
if(tf[f[i][j]-1] && tf[f[i][j]+1]) tot--;
else if(!tf[f[i][j]-1] && !tf[f[i][j]+1]) tot++;
tf[f[i][j]]=true;
}
}
printf("%d",ans);
}
第二题:给出n种面值的货币,要你求一种相同功能的货币并且是的面值尽量小。(相同功能指的是组成的价钱都能组成,不能组成的价钱都不能组成
那么把面值排序,然后看一下当前面值是否能被前面的面值组成,如果可以,那么就舍弃。否则就要选,并且跑一次完全背包。就完事儿了。.爆炸的几率不大。
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
bool tf[25010];
int T,n;
int d[110];
int ans=0,mmax=0;
int main(){
scanf("%d",&T);
while(T--){
scanf("%d",&n);
memset(tf,false,sizeof(tf));
mmax=0;
for(int i=1;i<=n;i++) scanf("%d",&d[i]),mmax=max(mmax,d[i]);
sort(d+1,d+1+n);
tf[0]=true;ans=0;
for(int i=1;i<=n;i++)
if(!tf[d[i]]){
ans++;
for(int k=0;k+d[i]<=mmax;k++) tf[k+d[i]]|=tf[k];
}
printf("%d\n",ans);
}
}
第三题:n个点的树,找一组方案,选出m条路径,使得m条路径中的最短路径最长。
这题我只会部分分,40到60.
问了师兄,先用二分答案,然后验证,首先每个节点的儿子产生一条向上的路径,那么把这些路径长度加上节点到儿子的边排一遍序,大于等于mid的直接成为一条路径,其他的找到与其匹配的最小的加起来大于等于mid成为一条路径,剩下的选最大的往上送即可。具体的实现就是排序,然后用一个并查集维护后面第一个没有被选过的路径即可。
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
int n,m;
struct edge{
int y,next,c,a;
}s[100010];
int first[50010],len=0,f[50010];
bool tf[50010];
int op=0;
int d[50010];
void ins(int x,int y,int c){
len++;
s[len]=(edge){y,first[x],c};first[x]=len;
}
int find_upper(int l,int r,int x){
int ans=0;
while(l<=r){
int mid=(l+r)/2;
if(d[mid]>=x){
r=mid-1;
ans=mid;
}
else l=mid+1;
}
return ans;
}
int findpa(int x){
if(f[x]!=x) return f[x]=findpa(f[x]);
return x;
}
int dfs(int x,int fa,int k){
int tot=0,temp;
for(int i=first[x];i!=0;i=s[i].next){
int y=s[i].y;
if(y==fa) continue;
s[i].a=dfs(y,x,k)+s[i].c;
}
for(int i=first[x];i!=0;i=s[i].next){
int y=s[i].y;
if(y==fa) continue;
if(s[i].a>=k) op++;
else d[++tot]=s[i].a;
}
sort(d+1,d+1+tot);
for(int i=1;i<=tot+1;i++) f[i]=i;
int ans=0;
for(int i=1;i<=tot;i++){
if(findpa(i)!=i) continue;
int x=find_upper(i+1,tot,k-d[i]);
if(x==0) continue;
int p=findpa(x);
if(p==tot+1) continue;
f[i]=f[i+1];
f[p]=f[p+1];op++;
}
int mmax=0;
for(int i=1;i<=tot;i++)
if(findpa(i)==i) mmax=max(mmax,d[i]);
return mmax;
}
bool check(int x){
op=0;
dfs(1,0,x);
return op>=m;
}
int main(){
scanf("%d %d",&n,&m);
int x,y,c;
int l=0,r=0;
for(int i=1;i<=n-1;i++){
scanf("%d %d %d",&x,&y,&c);
ins(x,y,c);
ins(y,x,c);
r+=c;
}
int ans=0;
while(l<=r){
int mid=(l+r)/2;
if(check(mid)) l=(ans=mid)+1;
else r=mid-1;
}
printf("%d\n",ans);
}
整一天看下来还是发挥得不怎么样,第三题其实挺好想的,平时应该多做点题来巩固。Day1期望250吧。
Day2
这一天我和brz都晚起了,所以就很困惑,赶快去小卖部买了两条巧克力一条给brz,就匆匆进考场了。
结果整套题看下来就自闭了。
一眼看到第一题好像挺难的,看了看数据范围。
发现要不是个树,要么是个基环树。对于树,我们把一个点的儿子排一遍序,然后遍历即可。如果是基环树,先找到那个环上面的边,把它们记录下来,然后枚举这些边中那些边不能走,剩下的跟树的做法一样。
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<iostream>
#include<queue>
using namespace std;
int n,m;
struct edge{
int y,next;
}s[10010];
int first[5010],len=1;
int ans[5010],isn=0,op[5010];
bool tf[5010];
int loop[5010],end;
bool fd,we;
void ins(int x,int y){
len++;
s[len]=(edge){y,first[x]};first[x]=len;
}
void dfs(int x,int fa,int *ans){
ans[++ans[0]]=x;
priority_queue<int , vector<int> , greater<int> > f;
for(int i=first[x];i!=0;i=s[i].next){
int y=s[i].y;
if(y==fa || i==isn || i==(isn^1)) continue;
f.push(y);
}
while(!f.empty()) {dfs(f.top(),x,ans);f.pop();}
}
void find_loop(int x,int fa){
tf[x]=true;
for(int i=first[x];i!=0;i=s[i].next){
int y=s[i].y;
if(y==fa) continue;
if(tf[y]){
fd=we=true;
loop[++loop[0]]=i;
end=y;
break;
}
find_loop(y,x);
if(fd){
if(we) loop[++loop[0]]=i;
if(x==end) we=false;
break;
}
}
tf[x]=false;
}
int main(){
scanf("%d %d",&n,&m);
int x,y;
for(int i=1;i<=m;i++){
scanf("%d %d",&x,&y);
ins(x,y);
ins(y,x);
}
if(m==n-1) dfs(1,0,ans);
else{
find_loop(1,0);
isn=loop[1];
dfs(1,0,ans);
for(int i=2;i<=loop[0];i++){
isn=loop[i];
op[0]=0;
dfs(1,0,op);
for(int j=1;j<=n;j++)
if(op[j]<ans[j]){
for(int k=j;k<=n;k++) ans[k]=op[k];
break;
}
else if(op[j]>ans[j]) break;
}
}
for(int i=1;i<=n;i++) printf("%d ",ans[i]);
}
第二题挺难的,在考场上只看出来了三条性质。
1.对于每一条自左下到右上的斜线,肯定是11111000000,这样子的,否则就肯定存在一组路径不满足条件。
2.对于10的交界处,它们右下的点到最后一个点所形成的矩阵是全0、全1.
3.n等于1时,答案为
没有考虑n等于2的时候。就很亏,应该连20分都难拿到。
其实跟正解接近了,下一步就是构造Dp。
挺多人都是直接找规律,发现当时,答案是,所以打打表,优化一下。有关证明我也不是很会,瞪眼法吧。
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<iostream>
using namespace std;
int d[9]={1,1,12,112,912,7136,56768,453504,3626752};
int d2[9]={1,1,36,336,2688,21312,170112,1360128,10879488};
int n,m;
long long ans=1;
long long mod=1e9+7;
long long ksm(long long x,long long t){
long long tot=1;
while(t){
if(t%2) (tot*=x)%=mod;
(x*=x)%=mod;
t/=2;
}
return tot;
}
int main(){
scanf("%d %d",&n,&m);
if(n>m) swap(n,m);
if(n==1) printf("%lld\n",ksm(2,m));
else if(n==m) printf("%d\n",d[n]);
else printf("%lld\n",d2[n]*ksm(3,m-n-1)%mod);
}
第三题:最难的一题,用了两天才搞定。
其实就是对于每一条边,两个端点至少有一个要被选,问最小代价。
反过来说,就是不选一些点,两两之间要独立,求最大独立集。
首先,这是一个非常经典的树形Dp的问题。
显然的。
nm即可。当要求的两个点在一起的时候,并且都为0。那么就输出-1.具体实现用map。
然后,要带修。其实选,就相当于把权值改成极小值,因为这样一定会选到那个点,不选就改成极大值,因为那样肯定不会选到它。
最后再还原一下答案就可以了。
就变成了动态Dp的裸题,把矩阵画出来即可。
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<iostream>
#include<map>
#define lc (now<<1)
#define rc ((now<<1)|1)
using namespace std;
int n,m;
long long w[100010];
struct edge{
int y,next;
}s[200010];
long long mmax=(long long)1e14;
struct matrix{
long long op[3][3];
matrix operator*(const matrix b)const{
matrix q;
for(int i=1;i<=2;i++)
for(int j=1;j<=2;j++){
q.op[i][j]=mmax;
for(int k=1;k<=2;k++)
q.op[i][j]=min(q.op[i][j],op[i][k]+b.op[k][j]);
}
return q;
}
}tr[300010];
map<pair<int , int> , int> mp;
int first[100010],len=0;
int tot[100010],son[100010];
int image[100010],fact[100010],last[100010],fa[100010],top[100010];
long long f[100010][2],g[100010][2];
char ch[3];
void ins(int x,int y){
len++;
s[len]=(edge){y,first[x]};first[x]=len;
}
void dfs_1(int x){
tot[x]=1;
for(int i=first[x];i!=0;i=s[i].next){
int y=s[i].y;
if(y==fa[x]) continue;
fa[y]=x;
dfs_1(y);
tot[x]+=tot[y];
if(tot[y]>tot[son[x]]) son[x]=y;
}
}
void dfs_2(int x,int tp){
image[x]=++len;fact[len]=x;top[x]=tp;
if(son[x]!=0) dfs_2(son[x],tp);
else {last[tp]=x;return ;}
for(int i=first[x];i!=0;i=s[i].next){
if(s[i].y==fa[x] || s[i].y==son[x]) continue;
dfs_2(s[i].y,s[i].y);
}
}
void tr_dp(int x){
f[x][1]=g[x][1]=w[x];
f[x][0]=g[x][0]=0;
for(int i=first[x];i!=0;i=s[i].next){
int y=s[i].y;
if(y==fa[x]) continue;
tr_dp(y);
f[x][1]+=min(f[y][0],f[y][1]);
f[x][0]+=f[y][1];
if(y==son[x]) continue;
g[x][1]+=min(f[y][0],f[y][1]);
g[x][0]+=f[y][1];
}
}
void update(int now,int l,int r,int x){
if(l==r){
tr[now].op[1][1]=mmax;tr[now].op[1][2]=g[fact[x]][0];
tr[now].op[2][1]=tr[now].op[2][2]=g[fact[x]][1];
return ;
}
int mid=(l+r)/2;
if(x<=mid) update(lc,l,mid,x);
else update(rc,mid+1,r,x);
tr[now]=tr[lc]*tr[rc];
}
matrix query(int now,int x,int y,int l,int r){
if(x==l && y==r) return tr[now];
int mid=(l+r)/2;
if(y<=mid) return query(lc,x,y,l,mid);
else if(mid<x) return query(rc,x,y,mid+1,r);
else return query(lc,x,mid,l,mid)*query(rc,mid+1,y,mid+1,r);
}
void build(int now,int l,int r){
if(l==r){
tr[now].op[1][1]=mmax;tr[now].op[1][2]=g[fact[l]][0];
tr[now].op[2][1]=tr[now].op[2][2]=g[fact[l]][1];
return ;
}
int mid=(l+r)/2;
build(lc,l,mid);build(rc,mid+1,r);
tr[now]=tr[lc]*tr[rc];
}
void change(int x,long long y){
g[x][1]+=y-w[x];
w[x]=y;
matrix A,B;
int st,ed;
while(1){
st=top[x];ed=last[top[x]];
A=query(1,image[st],image[ed],1,n);
update(1,1,n,image[x]);
B=query(1,image[st],image[ed],1,n);
x=fa[st];
if(x==0) break;
g[x][0]+=B.op[2][2]-A.op[2][2];
g[x][1]+=min(B.op[1][2],B.op[2][2])-min(A.op[1][2],A.op[2][2]);
}
}
int main(){
scanf("%d %d %s",&n,&m,ch);
for(int i=1;i<=n;i++) scanf("%lld",&w[i]);
int x,y;
for(int i=1;i<=n-1;i++) {
scanf("%d %d",&x,&y);
mp[make_pair(x,y)]=1;
ins(x,y);ins(y,x);
}
len=0;
dfs_1(1);dfs_2(1,1);
tr_dp(1);
build(1,1,n);
int a,b;
int temp;
long long xx,yy,to;
matrix A;
for(int i=1;i<=m;i++){
scanf("%d %d %d %d",&a,&x,&b,&y);
temp=0,to=0;
if(x==0 && y==0 && (mp[make_pair(a,b)] || mp[make_pair(b,a)])){
printf("-1\n");
continue;
}
xx=(x==0?mmax:(to++,temp+=w[a],-mmax));
yy=(y==0?mmax:(to++,temp+=w[b],-mmax));
x=w[a],y=w[b];
change(a,xx);
change(b,yy);
A=query(1,image[1],image[last[1]],1,n);
long long ans=min(A.op[1][2],A.op[2][2])+to*mmax+temp;
printf("%lld\n",ans);
change(a,x);
change(b,y);
}
}
我才不会告诉你我开了O2。
期望加起来大概在,但是我不知道NOIp这种非人类的数据会不会把我卡成爆零。
总的来说,还是不能心浮气躁,面对问题要仔细思考。在考场上不能做得比平时好很多,所以才要把平时做得更好啊,不断的比赛,不断地刷题,在每一次刷题后不断地总结,这才是成长啊!要是整日颓废,不过专心,还不如滚回去搞文化课?
在接下来的一段时间内,可能要做的就是两件事情吧:
1.巩固旧算法,学习新算法(FFT,NTT,莫比乌斯反演,平面几何虽然学了,但是总是不知道有什么应用,而且学得不精,需要刷题来与其他算法更好的结合吧)。
2.刷题:在GDKOI2018之前,在bzoj,借助别人的题表,刷够300题,题表会在本博客中贴出。平均每天3题,我一定行!