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


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

T1 「JOISC 2016 Day 3」电报

题目

点这里

考场思考

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

正解

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

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

题目

点这里

考场思考

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

正解

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

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

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

#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]\):走到点 \((i,j)\) 时的最小花费。
先初始化 memset(dp,127,sizeof dp)
特殊处理 \(dp[i][j]=-1\) 是柱子,不能走。
然后将第 \(0\) 排初始附 \(0\)
最后判最后一排是不是全为 2139062143 即可。
预估分数 \(100pts\),实际分数 \(40pts\)
不要问我为什么,题意理解错了...

#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]\)
然后我就几乎翻车了
但是改了之后也是 \(75pts\) ,后来发现题目还有一个点读错了...
这就是语文弱科的不好之处
后来发现,小鸟连续跳时,所跳高度是前一个的 \(x\)
我去这道题真是用心出样例,用脚造数据...
具体思路很好想,上升时完全背包,下落时 \(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(NM)\),空间复杂度 \(N(N+M)\),但是其实可以优化到 \(M\)

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

又是翻车的一天

posted @ 2020-01-18 22:25  Arextre  阅读(134)  评论(0编辑  收藏  举报