总结
多多注意数据范围 数据范围 数据范围!!!
D. Fragmentation merging
题意:
给定1–n的全排列,随机选取两个区间A,B, 产生C的条件为:A和B交集为空,且A和B并集中最大值-最小值 + 1为并集的大小。
求能产生多少种C。(不同种C中组成元素不同)
思路:
观察数据范围,n <= 1e4。提示可能有O(n ^ 2)做法。
假设枚举集合C中min值和max值, 那么能确定集合C中的元素。(C中元素必定是连续的)
考虑O(n ^ 2)是否可行。
[i, j]
[i, j + 1]
[i, j + 2]
[i, j +…]
判断当前[i, j]是否成立。 因为枚举i、j是从小到大枚举。
那么对于同一个i,j是逐渐增大的。
假设j枚举到了k,那么说明[i,k - 1]作为最大值已经遍历过了。那么如果[i, k]合法。说明此时组成的区间要么是1个或者2个。
暴力枚举最小值、最大值,根据区间的个数判断是否合法即可。
code:
int pos[maxn], book[maxn], a[maxn];
int main()
{
int T;
scanf("%d", &T);
while(T --)
{
int n;
scanf("%d", &n);
for(int i = 1 ; i <= n ; i ++)
scanf("%d", &a[i]), pos[a[i]] = i;
int res = 0;
if(n == 1)
printf("0\n");
else
{
for(int i = 1 ; i <= n ; i ++)
{
for(int j = 1 ; j <= n ; j ++)
book[j] = 0;
int cnt = 0;
for(int j = i ; j <= n ; j ++)
{
book[pos[j]] = 1;
if(pos[j] == 1)
{
if(pos[j] + 1 <= n && !book[pos[j] + 1]) cnt ++;
}
else if(pos[j] == n)
{
if(pos[j] - 1 >= 1 && !book[pos[j] - 1]) cnt ++;
}
else //左右都没有
{
if((pos[j] - 1 >= 1 && !book[pos[j] - 1]) && (pos[j] + 1 <= n && !book[pos[j] + 1])) cnt ++;
if((pos[j] - 1 >= 1 && book[pos[j] - 1]) && (pos[j] + 1 <= n && book[pos[j] + 1])) cnt --;
}
if(cnt <= 2)
res ++;
}
}
printf("%d\n",res);
}
}
return 0;
}
H. Information Transmission
题意:
给定一个有向图。如果从某点出发,能经过<=3条边再回到当前点,那么在之间的边上传递信号,信号不会衰弱,否则每次传递信号衰弱一次。求1号点到各个点的信号衰弱的最小次数。
思路:
根据题意,一个点能经过<=3次信号衰弱再次到达当前点,那么在此次路径上信号不会衰弱。可以看成当前路径间边权为0,否则边权为1。那么就将题目转化为了求1号点到其他点的最小距离的问题。
一个点经历 <= 3条边再次回到当前点。
那么有可能为 u—> v —> u 、 u – > v – > t – > u
考虑到数据范围n <= 500且路径中最多出现三个点,就不需要dfs缩点了。
直接暴力枚举从n个点中任选两点、三点之间的路径和是否<=3,如果<=3,那么之间的路径权值为0。
然后跑次floyed计算答案即可(floyed暴力思想yyds)。
code:
int dis[305][305];
int main()
{
int N, M;
scanf("%d %d", &N, &M);
for(int i = 1 ; i <= N ; i ++)
{
for(int j = 1 ; j <= N ; j ++)
{
if(i == j) dis[i][j] = 0;
else dis[i][j] = 1e6;
}
}
for(int i = 1 ; i <= M ; i ++)
{
int u, v;
scanf("%d %d", &u, &v);
dis[u][v] = 1;
}
for(int i = 1 ; i <= N ; i ++)
{
for(int j = 1 ; j <= N ; j ++)
{
if(i != j && dis[i][j] + dis[j][i] <= 3)
dis[i][j] = dis[j][i] = 0;
for(int k = 1 ; k <= N ; k ++)
{
if(i != j && i != k && j != k && dis[i][j] + dis[j][k] + dis[k][i] <= 3)
{
dis[i][j] = dis[j][k] = dis[k][i] = 0;
dis[i][k] = dis[j][i] = dis[k][j] = 0;
}
}
}
}
for(int k = 1 ; k <= N ; k ++)
{
for(int i = 1 ; i <= N ; i ++)
{
for(int j = 1 ; j <= N ; j ++)
{
if(dis[i][j] > dis[i][k] + dis[k][j])
dis[i][j] = dis[i][k] + dis[k][j];
}
}
}
for(int i = 1 ; i <= N ; i ++)
{
if(dis[1][i] == 1e6) printf("-1 ");
else printf("%d ", dis[1][i]);
}
return 0;
}
I. Sequence
题意:
给定一个长度为N的数组和M个限制。
限制形式:a b表示p[a] != b
(A,B,L,R):在区间[A,B]上任意一点为值域[L,R]上某值。
求有多少个(A, B,L, R)。
思路:
考虑直接计算很难, 需要容斥。
观察题目数据范围,(A,B,L,R) <= 5000。M <= 1e6
考虑解题时间复杂度O(n ^ 2) O(n ^ 2 log)
考虑题意给定的限制。
假设把其看作是矩阵中某个格子不能放东西。
类似(1为能放, 0为不能放)
1 0 1 0
0 0 1 0
1 1 0 0
1 0 1 0
那么合法的(A,B,L,R)数量为所有1能组成的矩形的个数。
题目就转化为给定n * n的矩阵,求其中所有能由1组成的矩形的个数(非常巧妙的转化)
那么题目就变成了很经典的问题,直接上板子。
code:
int mm[5010][5010], h[maxn], l[maxn], r[maxn];
int main()
{
int N, M;
scanf("%d %d", &N, &M);
for(int i = 1 ; i <= M ; i ++)
{
int a, b;
scanf("%d %d", &a,&b);
mm[a][b] = 1; //1不能放
}
ll sum = 0;
for(int i = 1 ; i <= N ; i ++)
{
for(int j = 1 ; j <= N ; j ++)
{
if(!mm[i][j]) h[j] ++;
else h[j] = 0;
}
stack <int> stk;
h[0] = h[N + 1] = -1;
for(int j = 1 ; j <= N + 1; j ++)
{
while(!stk.empty() && h[stk.top()] > h[j]) // 从当前点可以拓展 右边第一个 比他小的
r[stk.top()] = j, stk.pop();
stk.push(j);
}
while(!stk.empty()) stk.pop();
for(int j = N ; j >= 0 ; j --)
{
while(!stk.empty() && h[stk.top()] >= h[j]) // 左边第一个 >= h[j]的
l[stk.top()] = j, stk.pop();
stk.push(j);
}
for(int j = 1 ; j <= N ; j ++)
sum += (j - l[j]) * 1ll * (r[j] - j) * 1ll * h[j] * 1ll;
}
printf("%lld\n", sum);
return 0;
}