我的退役报告
解题日记
距离 NOIP还有100天
7.13
一个bool占1个字节,int 4字节 ,char 1字节 ,long long 8 字节
1KB = 1024B(字节)
1MB = 1024KB
1GB = 1024MB
[NOI2014] 起床困难综合症
在这道题中,限制为256MB = 268,435,456个字节
long long tn[100005];
int on[100005];
int flag,flag1,flag2;
bool vis[1000000000];
8 100005 +4 100005 + 1000000000 = 1,001,200,060
超限制了
而且有很多无用空间,导致本题直接从60 到 0。
sol:二进制数在三中操作中每一位都是独立的,不受其它位的影响。
我们希望原来的0都变1,如果原来的1不变
开两个量,一个所有位上都是0,另一个所有位上都是1,同时运算
运算结果逐个枚举,把两个量中的一都累加到ans中,并且注意那个全都是1的值不能大于M
时间复杂度位O(n)
大中锋的游乐场
sol: 带环的最短路,套上一个k值属性,每一个点的距离权值可能对应多个k值,所以用二维数组捆绑
分层图也可以但是适用于终点固定在一层,这里没有限定最后的k,所以分层图没有必要
考試的時候沒有綁定
孙老板去旅馆
区间修改,区间询问用线段树
本来用的一个sum表示这个区间的值,if(t[p].r - t[p].l + 1 == d && t[p].sum == d)来判断是否是这个区间
但二分该线段的时候,原来比如说
1 1 1 1 1 0我要4个
1 1 1 1 1 0
好了,本来成立的这样就不行了
所以一定要有三个量,pre,suf,maxn
maxn 判断这个线段里是否存在这个长度
pre,suf是用来拼接成这个长度并确定x的位置
jth的地獄門
const int N=4e2+5;
int t;
int g[N][N];//存图
int sum[N][N];//预处理每一列上的前缀和
int block[N];//上下边界全为1(不去顶角)+中间变成空白的次数
int bdr[N];//block[k-1]+右边界全为1(去掉顶角)的次数
int minn[N];//
int main()
{
int n,m;
scanf("%d%d",&n,&m);
for(int i=1;i<=n;++i)
for(int j=1;j<=m;++j)
scanf("%d",&g[i][j]);
for(int i=1;i<=n;++i)
for(int j=1;j<=m;++j)
sum[i][j]=sum[i-1][j]+g[i][j];
int ans=n*m;
for(int i=1;i<=n-5+1;++i)
for(int j=i+5-1;j<=n;++j)
{
int len=j-i-1;
for(int k=1;k<=m;++k)
{
int tot=sum[j-1][k]-sum[i][k];//num(1)
block[k]=block[k-1]+tot;
bdr[k]=block[k]-tot+(len-tot);//模擬k -1
block[k]+=2-g[i][k]-g[j][k];
}
/*
mmin[i]为i + 3 - 最后一列中bdr的最小值
bdr是1 - k列,k每次动,内部会变,对应的最小值也可能更小
mmin[i + 1]的范围比mmin[i]小,可以从mmin[i + 1]中找到最小值,但也会多出bdr[i + 3],(mmin[i]为i + 3 - 最后一列中bdr的最小值),所以要考虑bdr[i + 3]
预处理好后,tot为1列不包括顶角,tot重新赋值为····此時的mmin[k]
*/
minn[m-3]=bdr[m];
for(int k=m-4;k;--k)
minn[k]=min(minn[k+1],bdr[k + 3]);//minn[k]=min(minn[k+1],ber[k + 3]);
for(int k=1;k<=m-3;++k)
{
int tot=sum[j-1][k]-sum[i][k];
tot=minn[k]-block[k]+(len-tot);
ans=min(ans,tot);
}
}
printf("%d\n",ans);
return 0;
}
mmin[i]为i + 3 - 最后一列中bdr的最小值
bdr是1 - k列,k每次动,内部会变,对应的最小值也可能更小
mmin[i + 1]的范围比mmin[i]小,可以从mmin[i + 1]中找到最小值,但也会多出bdr[i + 3],(mmin[i]为i + 3 - 最后一列中bdr的最小值),所以要考虑bdr[i + 3]
预处理好后,tot为1列不包括顶角,tot重新赋值为••••此時的mmin[k]
这道题主要是想了解怎么用DP把枚举矩阵最大值的n4消为n3
7.14
sum
用倍增求LCA,树上的前缀和,ans = sum[x] + sum[y] - 2 * sum[lca] + val[lca]
反素数
结论题:1.任意一个合数都能看成若干个质数相乘,求因数个数套用排列组合结论公式。这样只要10个质数就能达到n的最大值,可以DFS
2.1 - n 中满足所有小于 n 且大于等于 1 的所有正整数的约数个数都小于 n 的约数个数的最大数,这其实就是求一个因数个数最大的最大数
3.如果两个数因数都是那个最大数,那么后面的那个数并不满足它前面的数都比它少,所以要取前面那个数
int num[1000]={0,2,3,5,7,11,13,17,19,23,29,31,33};
long long i,j,n,ans,best;
void dfs (long long x,long long y,int z){
if (x>best) {
best=x;
ans=y;
}
if(x==best&&ans>y)ans=y;//如果一个更小的数有相同的因数个数,那么他
if(z>11)return;
for(int i=1;i<=50;i++){
if(y*num[z]>n)break;
dfs(x*(i+1),y*num[z],z+1);
y*=num[z];
}
}
总结:一个长的判定条件可以分步去想,逐步缩小范围
[USACO07JAN] Balanced Lineup G
RMQ有线段树在线做法
ST离线做法
这道题用st就可以了
学长说要用树状数组拓展,似乎要加DFS递归,那这样还不如st呢,DFS容易写挂。总不会卡我这个吧。
注意ST查询是K不是K - 1
调了半天。
递增
答案是单调的,想到二分,但是学长要求树状数组。
st : 1 3 5 2 4 3
ed : 1 3 5 7 8 9 3
数的值小于下标,那么一定要改。
离散化一下
显然读入可以离散化。离散化以后把DP的状态定义改变一下,设f_i
为当前算到的以i为结尾的LIS的ans。显然f[i] = max(f[i],f[j + 1])(j < i>)
用树状数组维护最大值O(nlogn)
求出LIS
用n - LIS即可
还要考虑去重问题,如果有多个数字相同v.erase(unique(v.begin(), v.end()), v.end());
那就全部清除掉,因为是实数,这里的数要不满足条件都可以卡在一个小数里,但多余的肯定都要改的,只保留第一个就可以了。
7.16
[JLOI2011]不等式组
主要思路是将x提出来,得到x以此为树状数组的下标维护
细节很多
逃学的小孩
树的直径AB,设C点为k
ans = max(min(dis[a][k],dis[b][k]) + dis[a][b],ans);
小白逛公园
难点就在每次只改一个而不是一个区间,但求得是区间最大值而且不是整个区间
所以每个大区间都要拆成小区间,那么类比之前做过的孙老板去旅馆,都要开一个前缀和后缀
巡逻
首先,每条边会走两次,但是形成环后只用走一次,并加上1
可知,这个环越大越好
当在树的直径上加边时,环最大。
当k = 1时,找树的直径
当k = 2时,先找树的直径,在把第一个直径的边权全部改为-1,在找一个直径
7.18
排水系统
相当可惜
这道题是典型的DAG,但我没看出来
我打的代码并没有处理好流污水的先后顺序,我用广搜会出现重复读边,或者先后顺序有问题
做的题实在太少了
这道题考场上20分
重写60分
那年省三六十分
注意edge数组开两倍因为可能是双向边
受欢迎的牛
由题可得,受欢迎的奶牛只有可能是图中唯一的出度为零的强连通分量中的所有奶牛,所以若出现两个以上出度为0的强连通分量则不存在明星奶牛,因为那几个出度为零的分量的爱慕无法传递出去。那唯一的分量能受到其他分量的爱慕同时在分量内相互传递,所以该分量中的所有奶牛都是明星。
#include<bits/stdc++.h>
using namespace std;
const int N = 1e4 + 5,M = 5e4 + 5;
int n,m,x,y,hd[N],idx,timestamp,top,cnt;
int dfn[N],sum,res,siz[N],low[N],stk[N],out[N],id[N];
bool in_stk[N];
struct Edge{
int to,nxt;
}edge[M];
void add(int x,int y)
{
edge[++ idx].to = y;edge[idx].nxt = hd[x];hd[x] = idx;
}
void tarjan(int u){
dfn[u] = low[u] = ++ timestamp;
stk[ ++ top] = u, in_stk[u] = true;
for (int i = hd[u];i; i = edge[i].nxt)
{
int j = edge[i].to;
if (!dfn[j])
{
tarjan(j);
low[u] = min(low[u], low[j]);
}
else if (in_stk[j]) low[u] = min(low[u], low[j]);
}
if (dfn[u] == low[u])
{
++ cnt;
int y;
do {
y = stk[top -- ];
in_stk[y] = false;
id[y] = cnt;
siz[cnt] ++ ;
} while (y != u);
}
}
int main()
{
scanf("%d%d",&n,&m);
for(int i = 1;i <= m;++ i)
{
scanf("%d%d",&x,&y);
add(x,y);
}
for(int i = 1;i <= n;++ i)
if(!dfn[i])tarjan(i);
for (int i = 1; i <= n; i ++ )
for (int j = hd[i]; j; j = edge[j].nxt)
{
int k = edge[j].to;
int a = id[i], b = id[k];
if (a != b) out[a] ++ ;
}
for(int i = 1;i <= cnt;i ++)
{
if(!out[i])
{
++ sum;
res += siz[i];
}
if(sum >= 2){
res = 0;
break;
}
}
printf("%d",res);
return 0;
}
矩阵游戏
把他看成点与点的配对
用二分图的最大匹配去写
国王的游戏
贪心如何去证?看性质,反证法,设变量,得到不等式
7.19
Hankson 的趣味题
一道约数数论,gcd
上帝造题的七分钟 2 / 花神游历各国
难点在于模拟开根号下取整的操作
因为1e12的数开方6次就变成了1,所以需要修改的次数实际上很少,用并查集可以跳过小于等于1的数,然后树状数组单点修改即可。
离谱···
核心代码
add(i,(t=(int)sqrt(a[i]))-a[i]);
a[i]=t;
fa[i]=(a[i]<=1)?i+1:i;
i=(find(i)==i)?i+1:fa[i];
国王
状压
战略游戏
上司的舞会一样
最大子树和
剪枝条就是不要
同上司的舞会
中国象棋
dp[i][j][k]表示前i行,有j列只放了一个,有k列放了两个的方案数
第i行什么都不放:f[i][j][k]=f[i][j][k]+f[i−1][j][k]
.放1个在空的列。根据状态很容易发现空的列是m-j-k。那么方案显然了:f[i][j][k]=f[i][j][k]+f[i−1][j−1][k]×(m−(j−1)−k)
放1个在有1个的列。f[i][j][k]=f[i][j][k]+f[i−1][j+1][k−1]×(j+1)
放2个都在空的列。这里就用到排列了
······
中国剩余定理(CRT)/ 曹冲养猪
裸体
#include<bits/stdc++.h>
#define ll long long
ll n,a[16],m[16],Mi[16],mul=1,X;
inline int read()
{
int f=1,x=0;char s=getchar();
while(!isdigit(s)){if(s=='-')f=-1;s=getchar();}
while(isdigit(s)){x=x*10+s-'0';s=getchar();}
return x*f;
}
void exgcd(ll a,ll b,ll &x,ll &y){
if(b==0){x=1;y=0;return ;}
exgcd(b,a%b,x,y);
int z=x;x=y,y=z-y*(a/b);
}
int main(){
n=read();
for(int t=1;t<=n;++t){
int M=read();m[t]=M;
mul*=M;
a[t]=read();
}
for(int t=1;t<=n;++t){
Mi[t]=mul/m[t];
ll x=0,y=0;
exgcd(Mi[t],m[t],x,y);
X+=a[t]*Mi[t]*(x<0?x+m[t]:x);
}
printf("%lld",X%mul);
return 0;
}
[NOIP1999 提高组] 旅行家的预算
每次先用最便宜的油装满,有更便宜的就换,最后退油钱
用优先队列维护即可,细节题。
二叉苹果树
方程错了还有16p,逆天
树形背包,把能留几条边看成空间即可。
STA-Station
第一次看到换根
新根的子树(包括自己)深度都减少了1,原根的子树的深度都增加了1。
也就是说:对于x,它的父亲是y。
f[x]=f[y]-size[x]+n-size[x].
Treats for the Cows G/S
逆推区间DP
[HNOI2010]合唱队
逆推分类讨论区间DP
if(a[i]<a[i+1])f[i][j][0]+=f[i+1][j][0];
if(a[i]<a[j])f[i][j][0]+=f[i+1][j][1];
if(a[j]>a[i])f[i][j][1]+=f[i][j-1][0];
if(a[j]>a[j-1])f[i][j][1]+=f[i][j-1][1];
[CQOI2007]涂色
逆推 + 小贪心
7.21
货车运输
先根据题目判断有走最大路,小路被视为多余,所以要建最大生成树。
然后套倍增或树剖来维护
#include<bits/stdc++.h>
using namespace std;
#define maxn 100005
#define inf 999999999
struct Edge1{
int x,y,dis;
}edge1[maxn];
struct Edge2{
int to,nxt,w;
}edge2[maxn];
int cnt,n,m,hd[maxn],dep[maxn],f[maxn],fa[maxn][21],w[maxn][21],deep[maxn];
bool vis[maxn];
void add(int x,int y,int z)
{
edge2[++ cnt].nxt = hd[x];edge2[cnt].to = y;edge2[cnt].w = z;hd[x] = cnt;
}
bool cmp(Edge1 x,Edge1 y){
return x.dis > y.dis;
}
int find(int x)
{
if(f[x] == x) return f[x];
return f[x] = find(f[x]);
}
void kruskal()
{
sort(edge1 + 1,edge1 + m + 1,cmp);
for(int i= 1;i <= n;++ i)
f[i] = i;
for(int i = 1;i <= m;++ i)
{
if(find(edge1[i].x) != find(edge1[i].y))
{
f[find(edge1[i].x)] = find(edge1[i].y);
add(edge1[i].x,edge1[i].y,edge1[i].dis);
add(edge1[i].y,edge1[i].x,edge1[i].dis);
}
}
return;
}
void dfs(int node){
vis[node] = true;
for(int i = hd[node];i;i = edge2[i].nxt)
{
int to = edge2[i].to;
if(vis[to])continue;
deep[to] = deep[node] + 1;
fa[to][0] = node;
w[to][0] = edge2[i].w;
dfs(to);
}
return;
}
int lca(int x,int y)
{
if(find(x) != find(y))return -1;
int ans = inf;
if(deep[x] > deep[y]) swap(x,y);
for(int i=20; i>=0; i--)
if(deep[fa[y][i]]>=deep[x]){
ans=min(ans, w[y][i]);
y=fa[y][i];
}
if(x == y) return ans;
for(int i = 20;i >= 0;i --)
{
if(fa[x][i] != fa[y][i])
{
ans = min(ans,min(w[x][i],w[y][i]));
x = fa[x][i];
y = fa[y][i];
}
}
ans=min(ans, min(w[x][0], w[y][0]));
return ans;
}
int main()
{
int x,y,z,q;
scanf("%d%d",&n,&m);
for(int i=1; i<=m; i++){
scanf("%d%d%d",&x,&y,&z);
edge1[i].x=x;
edge1[i].y=y;
edge1[i].dis=z;
}
kruskal();
for(int i=1; i<=n; i++)
if(!vis[i]){
deep[i]=1;
dfs(i);
fa[i][0]=i;
w[i][0]=inf;
}
for(int i=1; i<=20; i++)
for(int j=1; j<=n; j++){
fa[j][i]=fa[fa[j][i-1]][i-1];
w[j][i]=min(w[j][i-1], w[fa[j][i-1]][i-1]);
}
scanf("%d",&q);
for(int i=1; i<=q; i++){
scanf("%d%d",&x,&y);
printf("%d\n",lca(x,y));
}
return 0;
}
三色二叉树
设 f[i][0/1/2]表示第i个节点染成 (绿 / 红 / 蓝) 时,它为根的子树中最多有多少绿色节点;g[i][0/1/2] 表示最少
f[x][0] = max(f[son1][1] + f[son2][2], f[son1][2] + f[son2][1]) + 1; //绿色+1
f[x][1] = max(f[son1][0] + f[son2][2], f[son1][2] + f[son2][0]);
f[x][2] = max(f[son1][0] + f[son2][1], f[son1][1] + f[son2][0]);
g[x][0] = min(g[son1][1] + g[son2][2], g[son1][2] + g[son2][1]) + 1;
g[x][1] = min(g[son1][0] + g[son2][2], g[son1][2] + g[son2][0]);
g[x][2] = min(g[son1][0] + g[son2][1], g[son1][1] + g[son2][0]);
七月集训
图论部分
BZOJ 4668: 冷战 并查集
共有两种操作,添加一条边或者询问两点在哪次操作后联通。强制在线。
void merge(int u,int v)
{
++tot;//写在return前!
if(u==v) return;//
if(siz[u]<siz[v]) swap(u,v);
fa[v]=u; siz[u]+=siz[v];
w[v]=tot;
}
void query(int u,int v)
{
int cr=u,d1=0,d2=0,f1,f2;
while(fa[cr]!=cr)d1++,cr=fa[cr]; f1=cr;
cr=v; while(fa[cr]!=cr)d2++,cr=fa[cr]; f2=cr;
if(f1!=f2){puts("0");ans=0;return;}
if(d1>d2) swap(u,v),swap(d1,d2);
ans=0;
while(d2>d1)
ans=max(ans,w[v]),v=fa[v],d2--;
while(u!=v)
ans=max(ans,max(w[u],w[v])),
u=fa[u],v=fa[v];
printf("%d\n",ans);
}
int main()
{
n=rdn(); m=rdn();
for(int i=1;i<=n;i++) fa[i]=i,siz[i]=1;
for(int i=1,op,u,v;i<=m;i++)
{
op=rdn(); u=rdn()^ans; v=rdn()^ans;
if(!op) merge(find(u),find(v));
else query(u,v);
}
return 0;
}
[POJ 2395] Out of Hay
有N(2-2000)个农场,M(1-10000)条通路连通各个农场,长度不超1e9,要求遍历全部的农场,且每走1单位长度就要消耗一单位水,每到一个农场可以把自己的水充满,求最小的水箱容量。
1≤N,M≤100000
这显然是最小生成树:要求遍历全部的农场,最小的水箱容量。
int n,m,ans;
int father[maxn];
struct data{int start,end,dist;}line[maxn];
bool cmp(data a,data b) {return a.dist<b.dist;}
void init(){for(int i=1;i<=n;i++) {father[i]=i;}}
int findfa(int x) {return x==father[x]?x:father[x]=findfa(father[x]);}
int main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++){
scanf("%d%d%d",&line[i].start,&line[i].end,&line[i].dist);
}
sort(line+1,line+1+m,cmp); init();
for(int i=1;i<=m;i++){
int j=findfa(line[i].start);
int k=findfa(line[i].end);
if(j==k) continue;
else father[j]=k;
ans=line[i].dist;
}
printf("%d\n",ans);
return 0;
}
P5994 [PA2014]Kuglarz
要知道一个区间的奇偶性有两种办法:
1.aii自己问自己
2.aki,aki-1都问
抽象成点与点建边。
定义[l,r]为(l,r]建边,如(0,1],(1,2]我们能推出(0,2],在一个大区间和两个小区间是由二得三,手玩样例可知最后每个点都必须联通,是一棵树,代价最小,即最小生成树。相当于加了个零点,然后顺利排除了每个只询问自己的情况。
当然这个诡异的思路肯定是人工造的,自然的思路的性质不能看出来但是变形一下就能看。
[题解 P5994 【PA2014]Kuglarz】 - 逃离地球 的博客 - 洛谷博客 (luogu.com.cn)
#include<bits/stdc++.h>
using namespace std;
const int N = 2e3 + 5;
int a[N][N],n,m;
struct edge{
int nxt,to,w;
}e[N*N*2];
int hd[N*N],cnt,dis[N*N],vis[N*N];
long long ans;
void add(int u,int v,int w)
{
e[++cnt]={hd[u],v,w};
hd[u]=cnt;
}
int main()
{
scanf("%d",&n);
for(int i = 1;i <= n;++ i)
for(int j = i,w;j <= n;++ j)
{
scanf("%d",&w);
add(i - 1,j,w);add(j,i - 1,w);
}
priority_queue<pair<int,int> > q;
memset(dis,0x3f,sizeof dis);
q.push(make_pair(0,1));dis[1] = 0;
while(!q.empty())
{
int x=q.top().second;q.pop();
if(vis[x]) continue;
vis[x]=1,ans+=dis[x];
for(int i=hd[x];i;i=e[i].nxt)
{
int y=e[i].to;
if(dis[y]>e[i].w) dis[y]=e[i].w,q.push(make_pair(-dis[y],y));
}
}
printf("%lld",ans);
return 0;
}
[BZOJ 3714] Kuglarz
因为到加油站就能加满油了,所以我们只需要满足油箱里的油能开到下一个加油站就行了.
也就是说,我们只需要将a,b两地的路径分成若干段加油站到加油站的路径,
如果最大值小于等于容量C就能到达.
那么,我们就可以把加油站拿出来建一个最小生成树,
其实,我们可以先求出离每个点i最近的加油站w[i]和距离d[j],
然后对于原图中的每条边x,y
如果w[x]!=w[y],就把w[x],w[y]连一条边,
距离就是d[x]+d[y]+wx,y.
而w[i],d[i]怎么求呢?
跑多源SPFA就行啦.
[BZOJ 2654] tree
给定一个n个点、m条边的带权无向图,每条边是黑色或白色。让你求一棵最小权的恰好有K条白色边的生成树。
边权和越大能容纳的边越多,二分答案。
如果我们给所有白色边增加边权,那么所选的白色边一定越来越少(反之同理)。所以我们二分给白色边增加多少边权,跑kruskal,最后再将增加的边权减去即可。
拓扑排序
给出一个N个点M条带边权边的有向图,判断这个图是否存在环?1≤N,M≤10^6
直接拓扑排序,如果拓扑排序不能把所有点都放入拓扑序中,则存在环。
[Haoi2016]食物链
递推。
P1347 排序
每建一次边就toposort,第一种情况略,第二种是有环,即搞出来有一些点没有在最后的队列里,第三种是点都在队列里,但点不够多,这里如果不是前两种就是第三种。