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

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

  • T1 「JOISC 2016 Day 3」电报
    • 题目
    • 考场思考
    • 正解
  • T2 「CQOI2016」路由表
    • 题目
    • 考场思考
    • 正解
  • T3 「NOIP2014」飞扬的小鸟
    • 题目
    • 考场思考
    • 正解


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

T1 「JOISC 2016 Day 3」电报

题目

点这里

考场思考

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

正解

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

#include
#include
#include
#include
#include
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」路由表

题目

点这里

考场思考

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

正解

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

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

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

#include
#include
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」飞扬的小鸟

题目

点这里

考场思考

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

#include
#include
#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]
然后我就几乎翻车了
但是改了之后也是 75 p t s 75pts 75pts ,后来发现题目还有一个点读错了…
这就是语文弱科的不好之处
后来发现,小鸟连续跳时,所跳高度是前一个的 x x x
我去这道题真是用心出样例,用脚造数据…
具体思路很好想,上升时完全背包,下落时 01 01 01 背包,再处理一些细节即可。
部分代码:
上升时考虑完全背包的做法 这里我太懒了,懒得特判,多打了一个循环来处理

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 ( N M ) O(NM) O(NM),空间复杂度 N ( N + M ) N(N+M) N(N+M),但是其实可以优化到 M M M

#include
#include
#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;
}

又是翻车的一天

你可能感兴趣的:(「小组联考」第二周三次考试)