zoukankan      html  css  js  c++  java
  • JSOI2010/2011 总结

    [JSOI2010]旅行

    题目链接

    挺神的一道 (dp) 题。枚举 (t),代表选择排序后前 (t) 条边在从 (1)(n) 新的路径上,设 (f_{i,j,k}) 表示到点 (i),经过前 (t) 条中的 (j) 条边,使用了 (k) 次交换边权的最短路,用最短路的形式进行 (dp)。转移有:

    (f_{to,j+1,k}=f_{u,j,k}+w_{j+1}),前 (t) 条边包括当前边。

    (f_{to,j+1,k+1}=f_{u,j,k}+w_{j+1}),前 (t) 条边不包括当前边并使用一次交换。

    (f_{to,j,k}=f_{u,j,k}+w),前 (t) 条边不包括当前边,不使用交换(此处 (w) 指当前边的边权)。

    时间复杂度大概是(O(nm^2k)),反正可以过。

    #include <cstdio>
    #include <cstring>
    #include <algorithm>
    #include <queue>
    #include <vector>
    #define inf (2100000000)
    typedef long long ll;
    inline int rd(){
    	int x=0,p=1;
    	char a=getchar();
    	while((a<48||a>57)&&a!='-')a=getchar();
    	if(a=='-')p=-p,a=getchar();
    	while(a>47&&a<58)x=(x<<1)+(x<<3)+(a&15),a=getchar();
    	return x*p;
    }
    inline int min(int x,int y){return x<y?x:y;}
    const int N=52,S=152;
    int n,m,s;
    struct Edge{
    	int u,v,w;
    }e[S];
    bool comp(const Edge &x,const Edge &y){
    	return x.w<y.w;
    }
    struct node{
    	int i,j,k;
    	node(int u=0,int e=0,int p=0){i=u,j=e,k=p;}
    };
    std::vector<int> p[N];
    int vis[N][S][N],f[N][S][N];
    int ans=inf;
    inline void bfs(int t){
    	std::queue<node> q;
    	memset(vis,0,sizeof vis),memset(f,0x3f,sizeof f);
    	f[1][0][0]=0,q.push(node(1,0,0)); 
    	while(!q.empty()){
    		node now=q.front();q.pop();
    		int u=now.i,j=now.j,k=now.k;vis[u][j][k]=0;
    		for(int i=0,sz=p[u].size();i<sz;i++){
    			int id=p[u][i],v=e[id].u;
    			if(v==u)v=e[id].v;
    			if(id<=t){
    				if(j<t&&f[u][j][k]+e[j+1].w<f[v][j+1][k]){
    					f[v][j+1][k]=f[u][j][k]+e[j+1].w;
    					if(!vis[v][j+1][k])vis[v][j+1][k]=1,q.push(node(v,j+1,k));
    				}
    			}
    			else{
    				if(j<t&&k<s&&f[u][j][k]+e[j+1].w<f[v][j+1][k+1]){
    					f[v][j+1][k+1]=f[u][j][k]+e[j+1].w;
    					if(!vis[v][j+1][k+1])vis[v][j+1][k+1],q.push(node(v,j+1,k+1));
    				}
    				if(f[u][j][k]+e[id].w<f[v][j][k]){
    					f[v][j][k]=f[u][j][k]+e[id].w;
    					if(!vis[v][j][k])vis[v][j][k]=1,q.push(node(v,j,k));
    				}
    			}
    		}
    	}
    }
    int main(){
    	n=rd(),m=rd(),s=rd();
    	for(int i=1;i<=m;i++)e[i].u=rd(),e[i].v=rd(),e[i].w=rd();
    	std::sort(e+1,e+m+1,comp);
    	for(int i=1;i<=m;i++)
    		p[e[i].u].push_back(i),p[e[i].v].push_back(i);
    	for(int i=0;i<=m;i++){
    		bfs(i);
    		for(int j=0;j<=s;j++)ans=min(ans,f[n][i][j]);
    	}
    	printf("%d
    ",ans);
    	return 0;
    }
    

    [JSOI2010]找零钱的洁癖

    题目链接

    正解不知道。

    由于种类数较少可以先暴力 (bfs),如果快爆了还没有搜到答案就及时结束,用贪心算出一个答案。然后就过了。

    求正解qwq

    #include <cstdio>
    #include <map>
    #define inf (210000000000ll)
    typedef long long ll;
    inline ll rd(){
    	ll x=0,p=1;
    	char a=getchar();
    	while((a<48||a>57)&&a!='-')a=getchar();
    	if(a=='-')p=-p,a=getchar();
    	while(a>47&&a<58)x=(x<<1)+(x<<3)+(a&15),a=getchar();
    	return x*p;
    }
    inline ll min(ll x,ll y){return x<y?x:y;}
    const int N=1000002;
    std::map<ll,int> mp;
    int n;
    ll x,a[N];
    ll v[N],s[N];
    int hd=0,tl=1;
    inline ll bfs(){
    	while(hd<tl){
    		hd++;
    		for(int i=1;i<=n;i++){
    			tl++;
    			if(tl==N-2)return inf;
    			s[tl]=s[hd]+1;
    			if(v[hd]<x)v[tl]=v[hd]+a[i];
    			else v[tl]=v[hd]-a[i];
    			if(v[tl]==x)return s[tl];
    			if(mp[v[tl]])tl--;
    			else mp[v[tl]]=1;
    		}
    	}
    	return inf;
    }
    inline ll get(ll x){
    	ll ans=0;
    	for(int i=n;i>=1;i--){
    		if(!a[i])continue;
    		ans+=x/a[i];
    		x-=x/a[i]*a[i];
    	}
    	if(x)return inf;
    	return ans;
    }
    int main(){
    	x=rd();
    	int i=1;ll k;
    	while(~scanf("%lld",&k))a[i++]=k;
    	n=i-1;
    	printf("%lld
    ",min(bfs(),get(x)));
    	return 0;
    }
    

    [JSOI2010]挖宝藏

    题目链接

    对于一个宝藏,如果要挖到它,则必须挖掉它上面的一个倒三角形,这个三角形会对应 (x) 轴的一段区间,这是可以直接算出来的。所以我们把每一个宝藏存在一个结构体里,包含这个区间、它本身的价值、挖到它至少得到的价值(即把上面的三角形中所有的宝藏的价值加起来)和挖它的代价。

    考虑 (dp)。设 (f_i) 表示挖前 (i) 个宝藏,第 (i) 个必须挖能得到的最大价值。

    先将宝藏按右端点从小到大排序,枚举 (1le j<i),如果 (j) 宝藏的区间与 (i) 的区间无交集,则可以直接取 (f_j) 进行转移。

    (i)(j) 有交,则维护一个指针,暴力处理重复的部分。由于宝藏的右端点是单调的,所以指针只会右移,复杂度仍然是 (O(n))

    总复杂度是 (O(n^2)),足以通过本题。

    #include <cstdio>
    #include <algorithm>
    typedef long long ll;
    inline int rd(){
    	int x=0,p=1;
    	char a=getchar();
    	while((a<48||a>57)&&a!='-')a=getchar();
    	if(a=='-')p=-p,a=getchar();
    	while(a>47&&a<58)x=(x<<1)+(x<<3)+(a&15),a=getchar();
    	return x*p;
    }
    inline int max(int x,int y){return x>y?x:y;}
    const int N=1002;
    int n;
    struct node{
    	int l,r,p1,p2,c;
    	bool operator < (const node &y)const{
    		return (r^y.r)?(r<y.r):(l<y.l);
    	}
    }a[N];
    int f[N],ans;
    inline int calc(int l,int r){
    	return (r-l+2)*(r-l+2)/4;
    }
    int main(){
    	n=rd();
    	for(int i=1;i<=n;i++){
    		int x=rd(),y=rd(),p=rd();
    		a[i].l=x+y+1,a[i].r=x-y-1,a[i].p1=p,a[i].p2=0,a[i].c=calc(x+y+1,x-y-1);
    	}
    	for(int i=1;i<=n;i++)
    		for(int j=1;j<=n;j++)
    			if(a[i].l<=a[j].l&&a[i].r>=a[j].r)a[i].p2+=a[j].p1;
    	std::sort(a+1,a+n+1);
    	for(int i=1;i<=n;i++){
    		f[i]=a[i].p2-a[i].c;
    		int now=1,sum=0;
    		for(int j=1;j<i;j++){
    			if(a[j].l<a[i].l&&a[j].r>=a[i].l){
    				while(now<=i&&a[now].r<=a[j].r){
    					if(a[now].l>=a[i].l)sum+=a[now].p1;
    					now++;
    				}
    				f[i]=max(f[i],f[j]+a[i].p2-a[i].c-(sum-calc(a[i].l,a[j].r)));
    			}
    			if(a[j].r<a[i].l)f[i]=max(f[i],f[j]+a[i].p2-a[i].c);
    		}
    	}
    	for(int i=0;i<=n;i++)ans=max(ans,f[i]);
    	printf("%d
    ",ans);
    	return 0;
    }
    

    [JSOI2010]排名

    题目链接

    对于第一问,从 (i) 向非零的 (a_i) 连边,用一个大根堆去跑拓扑排序,把先遇到的点的排名往后排。

    对于第二问,从非零的 (a_i)(i) 连边,遇到 (a_i=0)(i) 最开始就可以扔进大根堆,然后直接遍历,把先遇到的点的排名往前排。

    #include <cstdio>
    #include <queue>
    #include <cstring>
    typedef long long ll;
    inline int rd(){
    	int x=0,p=1;
    	char a=getchar();
    	while((a<48||a>57)&&a!='-')a=getchar();
    	if(a=='-')p=-p,a=getchar();
    	while(a>47&&a<58)x=(x<<1)+(x<<3)+(a&15),a=getchar();
    	return x*p;
    }
    const int N=200002;
    struct Edge{
    	int to,next;
    }edge[N<<1];
    int n,a[N];
    int head[N],cnt,in[N];
    int ans[N],now;
    inline void add(int f,int t){
    	edge[++cnt].next=head[f];
    	edge[cnt].to=t;
    	head[f]=cnt;
    }
    inline void init(){
    	memset(head,0,sizeof head);
    }
    inline void work1(){
    	std::priority_queue<int> q;
    	init();
    	for(int i=1;i<=n;i++){
    		if(!a[i])continue;
    		add(i,a[i]),in[a[i]]++;
    	}
    	for(int i=1;i<=n;i++)
    		if(!in[i])q.push(i);
    	now=n;
    	while(!q.empty()){
    		int u=q.top();q.pop();
    		ans[u]=now--;
    		for(int i=head[u];i;i=edge[i].next){
    			int v=edge[i].to;
    			if(!--in[v])q.push(v);
    		}
    	}
    	for(int i=1;i<=n;i++)printf("%d%c",ans[i]," 
    "[i==n]);
    }
    inline void work2(){
    	std::priority_queue<int> q;
    	init();
    	for(int i=1;i<=n;i++){
    		if(!a[i])q.push(i);
    		else add(a[i],i);
    	}
    	now=1;
    	while(!q.empty()){
    		int u=q.top();q.pop();
    		ans[u]=now++;
    		for(int i=head[u];i;i=edge[i].next){
    			int v=edge[i].to;
    			q.push(v);
    		}
    	}
    	for(int i=1;i<=n;i++)printf("%d%c",ans[i]," 
    "[i==n]);
    }
    int main(){
    	n=rd();
    	for(int i=1;i<=n;i++)a[i]=rd();
    	work1();
    	work2();
    	return 0;
    }
    

    [JSOI2011]柠檬

    题目链接

    斜率优化 (dp)。显然每一个小段首尾的 (s_i) 要相同,并且 (s_0) 也应选择这种 (s_i)。记录 (sum_i) 表示大小为 (s_i) 的贝壳在 (1-i) 中出现的次数,设 (f_i) 代表把前 (i) 个数分段得到的最大柠檬数,可以得到以下转移方程:

    [f_i=max_{1le jle i, s_i=s_j} {f_{j-1}+s_i*(sum_i-sum_j+1)^2} ]

    把式子拆开,得到:

    [f_{j-1}+s_j*sum_i^2=2*s_j*sum_j*(sum_i+1)+f_i-s_i*(sum_i+1)^2 ]

    (x=2*s_j*sum_j)(y=f_{j-1}+s_j*sum_i^2),使用单调栈维护上凸包。实现可以使用 (vector) 以节约空间。

    注意代码中的变量名和题解中的不太一样。

    #include <cstdio>
    #include <vector>
    typedef long long ll;
    inline ll rd(){
    	ll x=0,p=1;
    	char a=getchar();
    	while((a<48||a>57)&&a!='-')a=getchar();
    	if(a=='-')p=-p,a=getchar();
    	while(a>47&&a<58)x=(x<<1)+(x<<3)+(a&15),a=getchar();
    	return x*p;
    }
    inline ll max(ll x,ll y){return x>y?x:y;}
    const int N=100002,C=10002;
    int n;
    ll a[N],lst[N],s[N];
    ll f[N];
    std::vector <int> q[C];
    inline ll sqr(ll x){return x*x;}
    inline ll X(int i){return 2*a[i]*s[i];}
    inline ll Y(int i){return f[i-1]+a[i]*sqr(s[i]);}
    inline double slope(int i,int j){return 1.0*(Y(i)-Y(j))/(X(i)-X(j));}
    #define top q[now].size()-1
    int main(){
    	n=rd();
    	for(int i=1;i<=n;i++)a[i]=rd(),s[i]=s[lst[a[i]]]+1,lst[a[i]]=i;
    	for(int i=1;i<=n;i++){
    		int now=a[i];
    		while(q[now].size()>1&&slope(q[now][top],q[now][top-1])<slope(q[now][top],i))q[now].pop_back();
    		q[now].push_back(i);
    		while(q[now].size()>1&&slope(q[now][top],q[now][top-1])<s[i]+1)q[now].pop_back();
    		f[i]=f[q[now].back()-1]+a[i]*sqr(s[i]-s[q[now].back()]+1);
    	}
    	printf("%lld
    ",f[n]);
    	return 0;
    }
    

    [JSOI2011]分特产

    题目链接

    组合+容斥。若没有每一个人必须有的限制,答案为 (prodlimits_{i=1}^mdbinom{a_i+n-1}{n-1})。有了限制的话考虑容斥,枚举至少有 (i) 个没有,剩下的可以随便给。所以答案为:

    [ans=sumlimits_{i=0}^n(-1)^idbinom{n}{i}prodlimits_{j=1}^mdbinom{n-i-1+a_j}{n-i-1} ]

    #include <cstdio>
    typedef long long ll;
    inline int rd(){
        int x=0,p=1;
        char a=getchar();
        while((a<48||a>57)&&a!='-')a=getchar();
        if(a=='-')p=-p,a=getchar();
        while(a>47&&a<58)x=(x<<1)+(x<<3)+(a&15),a=getchar();
        return x*p;
    }
    const int N=5002,S=3000;
    const ll mod=1e9+7;
    ll fac[N],inv[N];
    int n,m;
    ll a[N],ans;
    inline ll fpow(ll b,ll p=mod-2){
    	ll ans=1,tmp=b;
    	while(p){
    		if(p&1)ans=ans*tmp%mod;
    		tmp=tmp*tmp%mod;
    		p>>=1;
    	}
    	return ans;
    }
    inline ll C(int n,int m){
    	return fac[n]*inv[m]%mod*inv[n-m]%mod;
    }
    int main(){
    	fac[0]=1;
    	for(int i=1;i<=S;i++)fac[i]=fac[i-1]*i%mod;
    	inv[S]=fpow(fac[S]);
    	for(int i=S-1;i>=0;i--)inv[i]=inv[i+1]*(i+1)%mod;
    	n=rd(),m=rd();
    	for(int i=1;i<=m;i++)a[i]=rd();
    	for(int i=0,p=0;i<=n;i++,p^=1){
    		ll t=C(n,i);
    		if(p)t=mod-t;
    		for(int j=1;j<=m;j++)t=t*C(n-i-1+a[j],n-i-1)%mod;
    		ans=(ans+t)%mod;
    	}
    	printf("%lld
    ",ans);
        return 0;
    }
    

    [JSOI2011]棒棒糖

    题目链接

    建一棵主席树,查询的时候对于每个节点判断一下,如果这个区间内 (le mid) 的数的个数满足条件就往左走,如果 (>mid) 的数的个数满足就往右走。都没有就是 (0)

    #include <cstdio>
    typedef long long ll;
    inline int rd(){
        int x=0,p=1;
        char a=getchar();
        while((a<48||a>57)&&a!='-')a=getchar();
        if(a=='-')p=-p,a=getchar();
        while(a>47&&a<58)x=(x<<1)+(x<<3)+(a&15),a=getchar();
        return x*p;
    }
    const int N=50002;
    int n,q,a[N],Size;
    int rt[N];
    int son[N<<5][2],val[N<<5];
    #define ls son[rt][0]
    #define rs son[rt][1]
    #define vl son[vs][0]
    #define vr son[vs][1]
    inline void update(int l,int r,int &rt,int vs,int x){
        rt=++Size;
        ls=vl,rs=vr,val[rt]=val[vs]+1;
        if(l==r)return;
        int mid=(l+r)>>1;
        if(x<=mid)update(l,mid,ls,vl,x);
        else update(mid+1,r,rs,vr,x);
    }
    inline int query(int l,int r,int rt,int vs,int k){
        if(l==r)return l;
        int mid=(l+r)>>1;
        if(val[vl]-val[ls]>k)return query(l,mid,ls,vl,k);
        else if(val[vr]-val[rs]>k)return query(mid+1,r,rs,vr,k);
        else return 0;
    }
    #undef ls
    #undef rs
    #undef vl
    #undef vr
    int main(){
        n=rd(),q=rd();
        for(int i=1;i<=n;i++)a[i]=rd(),update(1,n,rt[i],rt[i-1],a[i]);
        while(q--){
            int l=rd(),r=rd();
            printf("%d
    ",query(1,n,rt[l-1],rt[r],(r-l+1)>>1));
        }
        return 0;
    }
    

    [JSOI2011]任务调度

    题目链接

    虽然是挺裸的可并堆,但还是不想手写左偏树。。所以用 (pb\_ds) 水过去了,跑的还挺快?

    #include <cstdio>
    #include <algorithm>
    #include <map>
    #include<ext/pb_ds/priority_queue.hpp>
    typedef long long ll;
    inline int rd(){
    	int x=0,p=1;
    	char a=getchar();
    	while((a<48||a>57)&&a!='-')a=getchar();
    	if(a=='-')p=-p,a=getchar();
    	while(a>47&&a<58)x=(x<<1)+(x<<3)+(a&15),a=getchar();
    	return x*p;
    }
    const int N=300002,S=502;
    int n,m,t;
    int val[N];
    struct node{
    	int val,id;
    	node(int i=0,int v=0){val=v,id=i;}
    };
    struct cmp{
    	bool operator () (node x,node y){
    		return x.val>y.val;
    	}
    };
    __gnu_pbds::priority_queue<node,cmp> q[S];
    __gnu_pbds::priority_queue<node,cmp>::point_iterator it[N];
    int main(){
    	n=rd(),m=rd(),t=rd();
    	while(t--){
    		char s[10];int x,k,w;
    		scanf("%s",s);
    		if(s[0]=='A'){
    			x=rd(),k=rd(),w=rd();
    			val[k]=w;
    			it[k]=q[x].push(node(k,w));
    		}
    		if(s[0]=='D'){
    			x=rd(),k=rd(),w=rd();
    			val[k]-=w;
    			q[x].modify(it[k],node(k,val[k]));
    		}
    		if(s[0]=='T'){
    			x=rd(),k=rd();
    			q[k].join(q[x]);
    		}
    		if(s[0]=='M'){
    			x=rd();
    			printf("%d
    ",q[x].top().val);
    		}
    		if(s[0]=='W'){
    			x=rd(),w=rd();
    			node tmp=q[x].top();q[x].pop();
    			if((!q[x].empty())&&q[x].top().val==tmp.val){
    				puts("ERROR");
    				it[tmp.id]=q[x].push(tmp);
    			}
    			else{
    				k=tmp.id,val[k]+=w;
    				it[k]=q[x].push(node(k,val[k]));
    			}
    		}
    	}
    	return 0;
    }
    

    小结

    ( ext{JSOI2010/2011}) 的题目主要考察了 (dp) 及其优化、图论,数论和数据结构也有所涉及。其中俄罗斯方块、游戏两题暂时不会,找零钱那题也是通过分析数据的特殊性写了假算法,正解争取以后补上。通过这两年的做题,我发现自己的思维仍然不足以完成省选难度的部分题目,而且有些算法还是掌握的不太熟练,应该多做题以掌握足够的算法、技巧等。

  • 相关阅读:
    智能指针unique_ptr记录
    ubuntu系统火狐无法播放网页视频
    javascript中json对象json数组json字符串互转及取值
    C#压缩文件
    C#异步编程
    C# POST请求 json格式
    C# Http方式下载文件到本地类
    C#中NPOI操作excel之读取和写入excel数据
    浅析C#中抽象类和接口的区别
    C#自动实现Dll(OCX)控件注册的两种方法
  • 原文地址:https://www.cnblogs.com/wsk1202/p/12255606.html
Copyright © 2011-2022 走看看