2017 NOIp提高组 DAY2 试做
衔接 \(\text{DAY1}\)->\(2017\) \(\text{NOIp}\) 提高组 \(\text{DAY1}\) 试做
D2T1 奶酪
题库原题,很裸的并查集,就是建边相对复杂一些
D2T2 宝藏
题目描述
下方传送门
题目链接
上方传送门
思路分析
-
刚做的时候太着急了,上去就试着糊了个 \(\text{BFS}\),然后 \(\text{get}\) 到 \(40pts\)
-
后来看一眼数据范围,\(n\) 才这么小,直接暴搜,贪心和剪枝都不需要,直接枚举所有情况(全排列)就行,喜提 \(70pts\)
-
那要想 \(A\) 掉这道题,显然如果还是直接暴搜是过不了 \(12\) 这个点的(不过好像有大佬暴搜剪枝给过了),但是这样做至少正确性是有保证的,所以我们考虑如何能更快一些
-
再看一眼数据范围,正如林sir所说:
这数据范围不状压吗,疯狂暗示
再如学长说的:
——你们不会有人枚举全排列直接暴搜吧?
——那用啥?
——状压呀。 -
所以直接用状压再优化一下就好了,小 \(DP\)
\(Code\)
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<queue>
#define N 20
using namespace std;
inline int read(){
int x = 0,f = 1;
char ch = getchar();
while(ch>'9'||ch<'0'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
return x*f;
}
const int inf = 0x3f3f3f3f;
int n,m,e[N][N],dis[N],dp[1<<N],ans=inf;
bool flag[N][N];
void dfs(int x){
for(int i = 1;i <= n;i++){ //枚举未访问的点是由哪一个点转移过来的即可
if(1<<(i-1)&x){
for(int j = 1;j <= n;j++){
if(!(1<<(j-1)&x)&&flag[i][j]){
if(dp[1<<(j-1)|x]>dp[x]+dis[i]*e[i][j]){//
int pro = dis[j];
dis[j] = dis[i]+1;
dp[1<<(j-1)|x]=dp[x]+dis[i]*e[i][j];
dfs(1<<(j-1)|x);
dis[j] = pro; //搜完记得回溯
}
}
}
}
}
}
int main(){
memset(e,0x3f,sizeof(e));
n = read(),m = read();
for(int i = 1;i <= m;i++){
int u,v,w;u = read(),v = read(),w = read();
e[u][v] = e[v][u] = min(e[u][v],w);
flag[u][v] = flag[v][u] = 1;
}
for(int i = 1;i <= 12;i++){
memset(dis,0x3f,sizeof(dis));
memset(dp,0x3f,sizeof(dp));
dis[i] = 1;
dp[1<<(i-1)] = 0;
dfs(1<<(i-1));
ans = min(ans,dp[(1<<n)-1]);
}
printf("%d\n",ans);
return 0;
}
D2T3 列队
题目描述
下方传送门
题目链接
上方传送门
思路分析
暴力,30分,然后就不会了- 当然还是要有钻研精神的,于是继续
边看题解边想 - 对于行来说,每一行都有机会移动,然而对于列来说,移动的只有最后一列
- 每次移动,其实都相当于是一个删除操作再加上一个插入操作,所以可以用平衡树或者动态开点的线段树来维护(树状数组也彳亍,但我不会)
- 然后将行和列分开建树,每一行建一个,列只建最后一列就行了,然后考虑各种操作
- 查询和删除:,这两个是同步进行的,因为每次查询都对应一次删除。用线段树记录相应区间的删除个数,此时区间内的点数就是区间大小减去删除的。然后接下来考虑再删的时候,和查 \(K\) 大相似如果要删的数小于区间点数,就去左子树删,否则去右子树删
- 插入:开一个vector将所有插入的数装下就好
- 查询答案:
- 移动列,查询这一列线段树的第 \(x\) 个的位置 \(pos\),如果 \(pos<=n\) 说明是原来的移上去的,答案就是 \(pos*m\)。否则就是后来插进去的,输出 \(vector\) 里相应的元素即可
- 移动行,和上面的大同小异,行列颠倒即可,注意维护行的线段树内原有的节点是每一列的前 \(m-1\) 个,不包含最后一列
\(Code\)
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<vector>
#define N 300010
#define int long long
#define R register
using namespace std;
inline int read(){
int x = 0,f = 1;
char ch = getchar();
while(ch>'9'||ch<'0'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
return x*f;
}
int n,m,q,sum[N*20],lch[N*20],rch[N*20],rt[N],mx,pos,cnt;
vector<int>vc[N];
void modify(int &u,int l,int r){ //更新删除的点
if(!u)u = ++cnt;
sum[u]++;
if(l==r)return;
int mid = (l+r)>>1;
if(pos<=mid)modify(lch[u],l,mid);
else modify(rch[u],mid+1,r);
}
int query(int u,int l,int r,int w){ //查询位置
if(l==r)return l;
int mid = (l+r)>>1;
int tmp = mid-l+1-sum[lch[u]];
if(w<=tmp)return query(lch[u],l,mid,w);
else return query(rch[u],mid+1,r,w-tmp);
}
int get_ans(int x,int y){ //移动列
pos = query(rt[n+1],1,mx,x);
modify(rt[n+1],1,mx);
int res = pos<=n ? pos*m : vc[n+1][pos-n-1];
vc[n+1].push_back(y?y:res);
return res;
}
int get_ans1(int x,int y){ //移动行
pos = query(rt[x],1,mx,y);
modify(rt[x],1,mx);
int res = pos<m? (x-1)*m+pos : vc[x][pos-m];
vc[x].push_back(get_ans(x,res)); //再移一下列
return res;
}
signed main(){
n = read(),m = read(),q = read();
mx = max(n,m)+q;
for(int i =1;i <= q;i++){
int x,y;x = read(),y = read();
printf("%lld\n",y==m ? get_ans(x,0) : get_ans1(x,y));
}
return 0;
}
总结
- \(T1\) 并查集还算是比较水的
- \(T2\) 暴力分还是很足,当然正解也就是优化一下,并无太大本质区别
- \(T3\) 就有点ex了,30分有手就行,正解相当不友好,虽然线段树的代码很短但是不太好想,毕竟压轴题
- 再结合一下 \(D1\) 的,不翻车的话 \(300\) 分以上应该没啥问题, \(T1\) 都 \(A\) 掉的话在适当拿一些暴力即可,发挥好可以到 \(400\) 分左右,不过可能存在误差,毕竟环境不太一样
附洛谷对应题单->2017 NOIp