「小组联考」第二周三次考试


这次考试感觉迷迷糊糊的。
刚开始睡完午觉还没有清醒,然后就晕了大概半个小时。
然后就开始看题…结果这几个题又是大文章…
以为可以 AAAT3T3T3 ,结果因为题目原因被坑掉 70pts70pts70pts
看来这个考试可以不用考了…

T1 「JOISC 2016 Day 3」电报

题目

点这里

考场思考

迷迷糊糊,没思考,代码都没交过…

正解

首先,这个题的时间复杂度非常友好…
看看这个图的特性,发现它是基环树
所谓基环树,就是有 NNN 个点,NNN 条边 (部分特性)
那么,如果我们要将这样一个图变成一个强连通图,说明这样一个图一定是一个大环。
但是知道这些,似乎也不知道怎么做题…
这个题目,似乎没有说这些边拆掉之后怎么接,那么我们就不需要考虑怎么再把边接上去。
所以…嘿嘿嘿管那么多干嘛。
为了将这个图变成一个大环,我们先找出环,将不在环上的点拼成一条单链。
怎么做呢?
考虑一个不在环上的节点 uuu ,它的入度 in[u]in[u]in[u]
如果 in[u]>1in[u]>1in[u]>1,那么在连向 uuu 的边中,留下花费最大的,将其余的都切掉,至于怎么接,这不是我们考虑的范围…
但是如果 uuu 在环上,同理,留下花费最大的,将其余的都切掉。
但是有一个特殊的地方,每个环必须切掉一条边,不然它们就没有办法和其他的环或者单链练成一个大环。
具体实现

#include<cstdio>
#include<cstdlib>
#include<vector>
#include<queue>
#include<algorithm>
using namespace std;
#define int long long
#define rep(q,__a,__b) for(int q=__a,q##_end_=__b;q<=q##_end_;++q)
#define dep(q,__a,__b) for(int q=__a,q##_end_=__b;q>=q##_end_;--q)
template<class T>inline void qread(T& x){
    char c;bool f=false;x=0;
    while((c=getchar())<'0'||'9'<c)if(c=='-')f=true;
    for(x=(c^48);'0'<=(c=getchar())&&c<='9';x=(x<<1)+(x<<3)+(c^48));
    if(f)x=-x;
}
template<class T,class... Args>inline void qread(T& x,Args&... args){qread(x),qread(args...);}
inline int rqread(){
    char c;bool f=false;int x=0;
    while((c=getchar())<'0'||'9'<c)if(c=='-')f=true;
    for(x=(c^48);'0'<=(c=getchar())&&c<='9';x=(x<<1)+(x<<3)+(c^48));
    return f?-x:x;
}
template<class T>inline T Max(const T x,const T y){return x>y?x:y;}
template<class T>inline T Min(const T x,const T y){return x<y?x:y;}
template<class T>inline T fab(const T x){return x>0?x:-x;}
inline void getInv(int inv[],const int r,const int MOD)
{inv[0]=inv[1]=1;for(int i=2;i<=r;++i)inv[i]=1ll*inv[MOD%i]*(MOD-MOD/i)%MOD;}

const int MAXN=1e5;
const int INF=0x3f3f3f3f;

struct edge{
	int u,w;
	edge(){}
	edge(const int U,const int W):u(U),w(W){}
};
vector<edge>rG[MAXN+5];

int N,in[MAXN+5],fa[MAXN+5],ans,bel[MAXN+5],Ccnt,faw[MAXN+5],cirNode[MAXN+5];
bool vis[MAXN+5],broken[MAXN+5],flg;

inline void topo(){
	queue<int>Q;
	for(int i=1;i<=N;++i)if(in[i]==0)
		Q.push(i);
	if(Q.empty())flg=true;
	while(!Q.empty()){
		int now=Q.front();Q.pop();
		vis[now]=true,--in[fa[now]];
		if(in[fa[now]]==0)Q.push(fa[now]);
	}
	//处理属于哪个环
	for(int i=1,u;i<=N;++i)if(!vis[i]&&!bel[i]){//是环上点
		u=i,++Ccnt;
		cirNode[Ccnt]=i;
		while(bel[u]==0){
			bel[u]=Ccnt;
			u=fa[u];
		}
	}
	if(flg&&Ccnt==1)exit(0&puts("0"));
}

inline void init(){
	qread(N);
    for(int i=1;i<=N;++i){
    	qread(fa[i],faw[i]);
    	++in[fa[i]];
    	rG[fa[i]].push_back(edge(i,faw[i]));
    }
}

inline bool cmp(const edge a,const edge b){return a.w>b.w;}

inline void solvenode(){//处理入度多于 1 的点
	for(int i=1;i<=N;++i)if(rG[i].size()>1){
		sort(rG[i].begin(),rG[i].end(),cmp);
		for(int j=rG[i].size()-1;j>=1;--j){
			ans+=rG[i][j].w;
			if(bel[i]==bel[rG[i][j].u])broken[bel[i]]=true;
		}
	}
}

inline void breakcircle(){
	for(int i=1,j,k,mine;i<=Ccnt;++i)if(!broken[i]){//如果有环还没有被破开
		j=k=cirNode[i],mine=faw[j];
		do{
			// printf("Now k==%lld\n",k);
			if(rG[k].size()>1)mine=Min(mine,rG[k][0].w-rG[k][1].w);//可以保证,没有被删掉的边一定是环上边
			mine=Min(mine,faw[k]);
			k=fa[k];
		}while(k!=j);
		// printf("circle %lld:mine==%lld\n",i,mine);
		ans+=mine,broken[i]=true;
	}
}

signed main(){
	// freopen("04-20.in","r",stdin);
	init();
    topo();
    solvenode();
    // printf("After node:ans==%lld\n",ans);
    breakcircle();
    printf("%lld\n",ans);
    return 0;
}

T2 「CQOI2016」路由表

题目

点这里

考场思考

题目过长直接跳过系列
这道题…它的上辈子是论文吧…看到这长度直接跳过

正解

考完之后,耐下心读了遍题,发现这道题其实是可做的。
首先搞懂题意,掩码就是它会用到的长度,而其他没有用到的地方,直接扔掉就可以了…
操作 AAA
输入一个网址,加入进网址序列
操作 QQQ
询问一个网址 webwebweb,在 aaabbb 的网址加入网址序列时,webwebweb 的发送对象会变更多少次。
发送对象是什么呢?
就是能与其匹配,且最长最精准的网址
再仔细想想,这不就是 trietrietrie 树吗?
加入网址,就是在 trietrietrie 树上加入一个长度为 网址掩码 的二进制字符串。
但是因为询问是在不同的时间点,所以我们要建的是持久化 trietrietrie 树。
但是操作 QQQ 呢?
我们先来想想,发生发送对象变更现象并不是专业名称,只是作者自己取名下文简称发变现象)时,到底发生了什么。
从定义看,发变就是与询问网址 webwebweb 匹配,且最长精确的网址在添加 aaabbb 网址时,发生了变化。
为什么会发生变化?
假设我们在 iii 时刻,添加了网址 xxx ,在 jjj 时刻,添加了网址 yyy,令 yyy 对于询问 webwebweb 是优于 xxx 的。
那么,讨论 iiijjj 的关系

  • i<ji<ji<j 时,我们在时刻 iiij−1j-1j1,一定是用网址 xxx 的,而当到时刻 jjj到的时候,就会发生发变现象,发送对象 xxx 变成了 yyy
  • i>ji>ji>j 时,说明在 iii 之前就有比 xxx 更优的网址了,那么是不会发生发变现象的

所以,只需要求出 i<ji<ji<jyyy 优于 xxx 的情况就行了。
那么怎么处理呢?
一个好问题,但是我们看看这样的形式:
i<ji<ji<jyyy 优于 xxx
自然而然想到单调栈虽然我是在老师提醒下发现的
该怎么个单调法呢?
假若我们以时间顺序,放入网址,以网址的优劣为单调对象,那么时间复杂度是 O(b−a)O(b-a)O(ba) 级别
呵呵,1e61e61e6 玩个锤子 😃。
假若我们以网址优劣为顺序,放入时间。
那么复杂度最多也就 O(32)O(32)O(32)(链的长度)
再说说细节。
依次遍历这条链的节点 (从根到叶,即默认网址由劣到优),遇到一个结尾就考虑怎么放入其时间 ttt
假设栈顶 top<ttop<ttop<t ,很和谐,都是自己人。
但是如果 top>ttop>ttop>t ,和谐个屁,全都滚出去,然后…poppoppoptoptoptop 再一次小于 ttt才发现,哦,这是自己人,不要开枪…
然后…就是代码

#include<cstdio>
#include<stack>
using namespace std;
#define rep(q,a,b) for(int q=a,q##_end_=b;q<=q##_end_;++q)
#define dep(q,a,b) for(int q=a,q##_end_=b;q>=q##_end_;--q)
template<class T>inline void qread(T& x){
    char c;bool f=false;x=0;
    while((c=getchar())<'0'||'9'<c)if(c=='-')f=true;
    for(x=(c^48);'0'<=(c=getchar())&&c<='9';x=(x<<1)+(x<<3)+(c^48));
    if(f)x=-x;
}
template<class T,class... Args>inline void qread(T& x,Args&... args){qread(x),qread(args...);}
inline int rqread(){
    char c;bool f=false;int x=0;
    while((c=getchar())<'0'||'9'<c)if(c=='-')f=true;
    for(x=(c^48);'0'<=(c=getchar())&&c<='9';x=(x<<1)+(x<<3)+(c^48));
    return f?-x:x;
}
template<class T>inline T Max(const T x,const T y){return x>y?x:y;}
template<class T>inline T Min(const T x,const T y){return x<y?x:y;}
template<class T>inline T fab(const T x){return x>0?x:-x;}
inline void getInv(int inv[],const int r,const int MOD)
{inv[0]=inv[1]=1;for(int i=2;i<=r;++i)inv[i]=1ll*inv[MOD%i]*(MOD-MOD/i)%MOD;}

const int MAXM=1e6;

struct node{
	int son[2],siz,ed;
}tre[MAXM*32+5];
int rt[MAXM+5],Ncnt,cnt;

inline void add(const unsigned int v,const int len,const int t){
	rt[++cnt]=++Ncnt;
	int newnode=rt[cnt],pre=rt[cnt-1];
	tre[newnode]=tre[pre];
	for(int i=31,now;i>=31-len+1;--i){
		now=(v>>i)&1;//取出从高到低第 i 位
		pre=tre[pre].son[now];
		tre[newnode].son[now]=++Ncnt;
		newnode=tre[newnode].son[now];//同步向下

		tre[newnode]=tre[pre];//继承上一个点信息
		++tre[newnode].siz;
	}
	if(!tre[newnode].ed)tre[newnode].ed=t;
}

inline int getMaxl(int p,const unsigned int v){
	p=rt[p];
	int ret=0;
	for(int i=31,now;i>=0;--i){
		//这句话不能放到这里
		// if(tre[p].ed)ret=Max(ret,31-i+1);
		now=(v>>i)&1;
		if(tre[p].son[now])p=tre[p].son[now];
		else break;
		if(tre[p].ed)ret=Max(ret,31-i+1);
		//放到开头不行
	}
	return ret;
}

inline int query(int l,int r,const unsigned int v,const int len){
	l=rt[l],r=rt[r];
	stack<int>sta;
	for(int i=31,now;i>=0;--i){
		now=(v>>i)&1;
		if(tre[tre[r].son[now]].siz-tre[tre[l].son[now]].siz)//有地方可走
			r=tre[r].son[now],l=tre[l].son[now];
		else break;
		if(tre[r].ed&&31-i+1>len){//第二个条件的意思:这个点是否是掩码之内
			while(!sta.empty()&&tre[r].ed<sta.top())sta.pop();
			sta.push(tre[r].ed);
		}
	}
	return (int)sta.size();
}

char ord;
int M,len,l,r;
unsigned int a[4];

signed main(){
    qread(M);
   	for(int t=1;t<=M;++t){
    	scanf("%s",&ord);
    	if(ord=='A'){
    		scanf("%d.%d.%d.%d/%d",&a[0],&a[1],&a[2],&a[3],&len);
    		for(int i=1;i<=3;++i)a[0]=a[0]<<8|a[i];
    		add(a[0],len,t);
    	}
    	else{
    		scanf("%d.%d.%d.%d %d %d",&a[0],&a[1],&a[2],&a[3],&l,&r);
    		for(int i=1;i<=3;++i)a[0]=a[0]<<8|a[i];
    		len=getMaxl(l-1,a[0]);
    		printf("%d\n",query(l-1,r,a[0],len));
    	}
    }
    return 0;
}

思路来源大佬的博客


T3 「NOIP2014」飞扬的小鸟

题目

点这里

考场思考

考试的时候,发现这道题其实很水,然后就开始骗分…
定义 dp[i][j]dp[i][j]dp[i][j]:走到点 (i,j)(i,j)(i,j) 时的最小花费。
先初始化 memset(dp,127,sizeof dp)
特殊处理 dp[i][j]=−1dp[i][j]=-1dp[i][j]=1 是柱子,不能走。
然后将第 000 排初始附 000
最后判最后一排是不是全为 2139062143 即可。
预估分数 100pts100pts100pts,实际分数 40pts40pts40pts
不要问我为什么,题意理解错了…

#include<cstdio>
#include<cstring>
#define rep(q,a,b) for(int q=a,q##_end_=b;q<=q##_end_;++q)
#define dep(q,a,b) for(int q=a,q##_end_=b;q>=q##_end_;--q)
template<class T>inline void qread(T& x){
    char c;bool f=false;x=0;
    while((c=getchar())<'0'||'9'<c)if(c=='-')f=true;
    for(x=(c^48);'0'<=(c=getchar())&&c<='9';x=(x<<1)+(x<<3)+(c^48));
    if(f)x=-x;
}
template<class T,class... Args>inline void qread(T& x,Args&... args){qread(x),qread(args...);}
inline int rqread(){
    char c;bool f=false;int x=0;
    while((c=getchar())<'0'||'9'<c)if(c=='-')f=true;
    for(x=(c^48);'0'<=(c=getchar())&&c<='9';x=(x<<1)+(x<<3)+(c^48));
    return f?-x:x;
}
template<class T>inline T Max(const T x,const T y){return x>y?x:y;}
template<class T>inline T Min(const T x,const T y){return x<y?x:y;}
template<class T>inline T fab(const T x){return x>0?x:-x;}
inline void getInv(int inv[],const int r,const int MOD)
{inv[0]=inv[1]=1;for(int i=2;i<=r;++i)inv[i]=1ll*inv[MOD%i]*(MOD-MOD/i)%MOD;}

const int MAXN=1e4;
const int MAXM=1e3;

int N,M,K,x[MAXN+5],y[MAXN+5];
int dp[MAXN+5][MAXM+5];
bool flg[MAXN+5];
int pre[MAXN+5],lst;

inline void special(){
	puts("0");
	for(int i=1;i<=N;++i){
		pre[i]=pre[i-1]+flg[i-1];
	}
	printf("%d\n",pre[lst]);
}

signed main(){
	qread(N,M,K);
	for(int i=0;i<N;++i)qread(x[i],y[i]);
	memset(dp,127,sizeof dp);
	for(int i=1,p,l,h;i<=K;++i){qread(p,l,h);
		flg[p]=true;
		for(int j=0;j<l;++j)dp[p][j]=-1;//理解错误部分
		for(int j=h+1;j<=M;++j)dp[p][j]=-1;//理解错误部分
	}
	for(int j=0;j<=M;++j)if(dp[0][j]!=-1)dp[0][j]=0;
	for(int i=0;i<N;++i)for(int j=0;j<=M;++j)if(dp[i][j]!=-1){
		if(i!=1&&j==0)continue;
		if(j-y[i]>=1){
			if(dp[i+1][j-y[i]]!=-1)dp[i+1][j-y[i]]=Min(dp[i+1][j-y[i]],dp[i][j]);
		}
		if(j+x[i]>M){
			if(dp[i+1][M]!=-1)dp[i+1][M]=Min(dp[i+1][M],dp[i][j]+1);
			if(dp[i][M]!=-1)dp[i][M]=Min(dp[i][M],dp[i][j]+1);//理解错误部分
		}
		else{
			if(dp[i+1][j+x[i]]!=-1)dp[i+1][j+x[i]]=Min(dp[i+1][j+x[i]],dp[i][j]+1);
			if(dp[i][j+x[i]]!=-1)dp[i][j+x[i]]=Min(dp[i][j+x[i]],dp[i][j]+1);//理解错误部分
		}
		if(dp[i][j]!=2139062143)lst=i;
	}
	int ans=2139062143;
	for(int i=1;i<=M;++i)ans=Min(ans,dp[N][i]);
	if(ans==2139062143)special();
	else printf("1\n%d\n",ans);
    return 0;
}

正解

其实,题目所说的可以通过的管道漏洞是 (l,h)(l,h)(l,h) 而不是 [l,h][l,h][l,h]
然后我就几乎翻车了
但是改了之后也是 75pts75pts75pts ,后来发现题目还有一个点读错了…
这就是语文弱科的不好之处
后来发现,小鸟连续跳时,所跳高度是前一个的 xxx
我去这道题真是用心出样例,用脚造数据…
具体思路很好想,上升时完全背包,下落时 010101 背包,再处理一些细节即可。
部分代码:
上升时考虑完全背包的做法 这里我太懒了,懒得特判,多打了一个循环来处理

for(int j=x[i-1]+1;j<=x[i-1]+M;++j)
	dp[i][j]=Min(dp[i][j-x[i-1]]+1/*上一列连续按*/,dp[i-1][j-x[i-1]]+1/*只按了一下*/);
for(int j=M+1;j<=M+x[i-1];++j)
	dp[i][M]=Min(dp[i][M],dp[i][j]);//最后把跳出去的部分全部归到 dp[i][M] 中

下降的时候,直接从上一列转移过来即可

for(int j=1;j<=M-y[i-1];++j)
	dp[i][j]=Min(dp[i][j],dp[i-1][j+y[i-1]]);//注意此处没有 +1

最后把柱子部分特殊处理一下

for(int j=1;j<l[i];++j)dp[i][j]=INF;
for(int j=h[i]+1;j<=M;++j)dp[i][j]=INF;

下面是完全代码,时间复杂度 O(NM)O(NM)O(NM),空间复杂度 N(N+M)N(N+M)N(N+M),但是其实可以优化到 MMM

#include<cstdio>
#include<cstring>
#define rep(q,a,b) for(int q=a,q##_end_=b;q<=q##_end_;++q)
#define dep(q,a,b) for(int q=a,q##_end_=b;q>=q##_end_;--q)
template<class T>inline void qread(T& x){
    char c;bool f=false;x=0;
    while((c=getchar())<'0'||'9'<c)if(c=='-')f=true;
    for(x=(c^48);'0'<=(c=getchar())&&c<='9';x=(x<<1)+(x<<3)+(c^48));
    if(f)x=-x;
}
template<class T,class... Args>inline void qread(T& x,Args&... args){qread(x),qread(args...);}
inline int rqread(){
    char c;bool f=false;int x=0;
    while((c=getchar())<'0'||'9'<c)if(c=='-')f=true;
    for(x=(c^48);'0'<=(c=getchar())&&c<='9';x=(x<<1)+(x<<3)+(c^48));
    return f?-x:x;
}
template<class T>inline T Max(const T x,const T y){return x>y?x:y;}
template<class T>inline T Min(const T x,const T y){return x<y?x:y;}
template<class T>inline T fab(const T x){return x>0?x:-x;}
inline void getInv(int inv[],const int r,const int MOD)
{inv[0]=inv[1]=1;for(int i=2;i<=r;++i)inv[i]=1ll*inv[MOD%i]*(MOD-MOD/i)%MOD;}

const int MAXN=1e4;
const int MAXM=1e3;
const int INF=66666666;//十分吉祥

int dp[MAXN+5][(MAXM<<1)+5];
int l[MAXN+5],h[MAXN+5];
int x[MAXN+5],y[MAXN+5];
int N,M,K,ans2;bool flg[MAXN+5],lst;

signed main(){
	qread(N,M,K);
	for(int i=0;i<N;++i)qread(x[i],y[i]),l[i]=1,h[i]=M;
	l[N]=1,h[N]=M;
	for(int i=1,p,a,b;i<=K;++i){qread(p,a,b);
		flg[p]=true;
		l[p]=a+1;
		h[p]=b-1;
	}
	memset(dp,0x3f,sizeof dp);
	for(int j=0;j<=M;++j)dp[0][j]=0;
	for(int i=1;i<=N;++i){
		lst=false;
		for(int j=x[i-1]+1;j<=x[i-1]+M;++j)
			dp[i][j]=Min(dp[i][j-x[i-1]]+1,dp[i-1][j-x[i-1]]+1);
		for(int j=M+1;j<=M+x[i-1];++j)
			dp[i][M]=Min(dp[i][M],dp[i][j]);
		for(int j=1;j<=M-y[i-1];++j)
			dp[i][j]=Min(dp[i][j],dp[i-1][j+y[i-1]]);
		for(int j=1;j<l[i];++j)dp[i][j]=INF;
		for(int j=l[i];j<=h[i];++j)if(dp[i][j]<INF){lst=true;break;}//计算这一列可不可以到
		for(int j=h[i]+1;j<=M;++j)dp[i][j]=INF;
		if(lst&&flg[i])++ans2;//处理一下第二个答案
	}
	int ans=INF;
	for(int j=1;j<=M;++j)ans=Min(ans,dp[N][j]);
	if(ans<INF)return 0&printf("1\n%d\n",ans);
	else printf("0\n%d\n",ans2);
    return 0;
}

又是翻车的一天

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值