6/13
剩下的先鸽了,以后再补~
A Mr. Panda and Dominoes
B Prefix Code
大意:
给出n个字符串,问对于每一个字符串,是否是其他字符串的前缀,是的话输出No
思路:
对于每个字符串,取出它的前缀加入map中,最后判断每个字符串对应的值是否大于等于2即可
#include<bits/stdc++.h>
using namespace std;
const int N = 1e6 + 5;
int t, n, k, flag;
string s[N];
int main(){
scanf("%d", &t);
while(t--){
k++;
flag = 1;
scanf("%d", &n);
unordered_map<string,int> mp;
for (int i = 0; i < n;i++){
cin >> s[i];
if(mp[s[i]]){
flag = 0;
}
//mp2[s[i]]++;
string temp = "";
for (int j = 0; j < s[i].size(); j++){
temp.push_back(s[i][j]);
mp[temp]++;
}
}
for (int i = 0; i < n;i++){
if(mp[s[i]]>=2){
flag=0;
break;
}
}
if (flag)
{
printf("Case #%d: Yes
", k);
}
else{
printf("Case #%d: No
", k);
}
}
return 0;
}
C Maze
D Spanning Tree Removal
大意:
给出一个n个节点的完全图,每次删掉一个生成树的(n-1)个边,问最多能删几次,输出每次删哪些边
思路:
因为n个节点的完全图一共有((n-1)n/2)个边,所以一共能删(n/2)次,但是怎么删边呢?队友想出了一种方法:
就是将1到n按照顺时针排成一圈,然后每次选择1,2,6,3,5,4这样的类似“螺旋”的线生成一个树即可
#include<bits/stdc++.h>
using namespace std;
const int N = 1e6 + 5;
int t, n,cases;
int main(){
cin >> t;
while(t--){
cin >> n;
cases++;
int now = 1;
map<pair<int, int>, int> mp;
int time = n / 2;
printf("Case #%d: %d
", cases, time);
while(now<=time){
for (int i = 0; i < (n - 1);i++){
if((i&1)==0){
int a = (now - (i / 2)+n) % (n);
if(a==0)
a = n;
int b = (now + (i / 2) + 1) % (n);
if(b==0)
b = n;
cout << a << ' ' << b << endl;
}
else{
int a = (now + (i / 2) + 1) % n;
if(a==0){
a = n;
}
int b = (now - (i / 2 + 1) + n) % n;
if(b==0){
b = n;
}
cout << a << ' ' << b << endl;
}
}
now++;
}
}
return 0;
}
E Cave Escape
大意:
给出一个(n*m)的矩阵,从一个位置((i,j))可以移动到相邻的位置((x,y)),如果移动到的这个位置是第一次访问,那么得到收益为(V_{i,j}*V_{x,y}),否则为0,问从(S_r,S_c)移动到(T_r,T_c)的最大收益是多少,走到终点时,可以不结束而继续访问别的格子。
其中,(V_{i,j})的大小由如下公式确定:
(X_i=(A*X_{i-1}+B*X_{i-2}+C)mod(P))
(V_{i,j}=X_{(i-1)*m+j})
思路:
一开始被给出的公式搞晕了,以为肯定有什么深意,但是最后发现就是没啥用,只是求(V_{i,j})的公式而已....由于每个点只能贡献一次收益,所以如果我们将每个相邻的点之间连边,边权设为元素权值的积的话,可以看出就是找到一个最大生成树即可,所以跑一遍(kruskal)算法,将算法中的排序改成降序即可。
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 2e6 + 5;
int t, n, m, sr, sc, tr, tc,f[2][2]={0,1,1,0},num=0,p[N],cases=0;
LL x[N], A, B, C, P,v[1005][1005];
struct node{
int u, v;
LL w;
bool operator< (const node &W)const
{
return w > W.w; //重载运算符,因为是最大生成树,所以要return >
}
}edge[N];
int find(int x)
{
if (x != p[x]) p[x] = find(p[x]);
return p[x];
}
LL kruskal()
{
sort(edge, edge + num); // 排序,使得从最大权值的边开始
LL res = 0;
int cnt = 0; // res记录最大生成树的权值,cnt记录当前最大生成树内有几条边
for (int i = 1; i <= n*m; ++i) p[i] = i; // 并查集初始化
for (int i = 0; i < num ; ++i) // 枚举每一条边
{
int a = edge[i].u, b = edge[i].v, w = edge[i].w; // 边a, b, 权值为w
a = find(a), b = find(b); // 得到a的父节点和b的父节点
if (a != b) // 判断a和b是否在同一个集合内
{
res += w; // 在的话放入集合内
cnt++;
p[a] = b;
}
}
return res;
}
int main(){
cin>>t;
while(t--){
num = 0;
cases++;
cin >> n >> m >> sr >> sc >> tr >> tc;
cin >> x[1] >> x[2] >> A >> B >> C >> P;
for (int i = 3; i <= n*m;i++){
x[i] = (A * x[i - 1] + B * x[i - 2] + C) % P;
}
for (int i = 1; i <= n;i++){
for (int j = 1; j <= m;j++){
v[i][j] = x[(i - 1) * m + j];
}
}
for (int i = 1; i <= n;i++){
for (int j = 1; j <= m;j++){
for (int k = 0; k < 2;k++){ //给相邻的两个点连边,只需要连下方的和右边的即可,避免重复
int xx = i + f[0][k];
int yy = j + f[1][k];
if (xx <= n && yy <= m){
node temp;
temp.u = (i - 1) * m + j;
temp.v = (xx - 1) * m + yy;
temp.w = x[(i - 1) * m + j] * x[(xx - 1) * m + yy];
edge[num++]=temp;
}
}
}
}
LL res = kruskal();
printf("Case #%d: %lld
", cases, res);
}
return 0;
}
F A Simple Problem On A Tree
题意:
给定一颗树,每个点带点权,维护四个操作:
1 u v w : 将 u 、v 路径上的权值修改为 w
2 u v w : 将 u 、v 路径上的权值加上 w
3 u v w : 将 u 、v 路径上的权值乘上 w
4 u v : 询问 u 、v 路径上点权的立方和
思路:
树链剖分+线段树,维护区间立方和
由于开的空间过大,所以不能用数组实现邻接表 直接上vector
#include<bits/stdc++.h>
using namespace std;
const int N = 1e5+5;
typedef long long LL;
const int p = 1e9 + 7;
int tot, num;
int n, m, r,t,cases=0;
int w[N], a[N], sum1[N * 4], sum2[N * 4], sum3[N * 4], lazy_mul[N * 4], lazy_add[N * 4]; // w[i]=j表示时间戳为i的点的值为j,a[]输入每个节点的值,dat线段树每个点权值,lazy线段树每个点的懒标记
//int h[N], e[N * 2], ne[N * 2], idx; // 邻接表数组
int dep[N], dfn[N], wson[N], sz[N], top[N], fa[N]; // dep深度 dfn搜索序 wson重儿子 size子树大小 top链头 fa父节点
vector<int> mp[N];
/*
void add(int a, int b)
{
e[idx] = b, ne[idx] = h[a], h[a] = idx++;
}*/
// 得到sz, fa, dep, wson数组
void dfs1(int u)
{
dep[u] = dep[fa[u]]+1;
sz[u] = 1;
for(int i = 0; i<mp[u].size(); i++)
{
int j=mp[u][i];
if(j == fa[u]) continue;
fa[j] = u;
dfs1(j);
sz[u] += sz[j];
if(sz[j] > sz[wson[u]]) wson[u] = j; // 这里要注意根节点不能设为0,否则根节点的最重链无法更新,始终为0
}
}
// 得到dfn, top数组
void dfs2(int u, int nowtop)
{
dfn[u] = ++num;
w[num] = a[u];
//以搜索序重排权值
top[u] = nowtop;
if(wson[u]) dfs2(wson[u], nowtop); // 先搜索重儿子
for(int i = 0; i<mp[u].size(); i++) // 然后搜索轻儿子
{
int y=mp[u][i];
if(y ==fa[u]||y == wson[u]) continue;
dfs2(y, y);
}
}
void solve(int rt,int len,int a,int b){ //a为add b为mul
lazy_mul[rt] = 1ll*lazy_mul[rt] * b % p;
lazy_add[rt] = 1ll*lazy_add[rt] * b % p;
lazy_add[rt] = ((lazy_add[rt] + a) % p + p) % p;
if(b!=1){ //先乘后加
sum1[rt] = 1ll*sum1[rt] * b % p;
sum2[rt] = (1ll*sum2[rt] * b % p) * b % p;
sum3[rt] = ((1ll*sum3[rt] * b % p) * b % p) * b % p;
}
if(a!=0){
int a2 = 1ll*a * a % p, a3 = 1ll*a2 * a % p;
sum3[rt] = ((sum3[rt] + (LL)len * a3 % p) + p) % p;
sum3[rt] = ((sum3[rt] + 3ll * (LL)sum2[rt] % p * a % p) + p) % p;
sum3[rt] = ((sum3[rt] + 3ll * (LL)sum1[rt] % p * a2 % p) + p) % p;
sum2[rt] = ((sum2[rt] + 2ll * (LL)sum1[rt] % p * a % p) + p) % p;
sum2[rt] = ((sum2[rt] + (LL)len * a2 % p) + p) % p;
sum1[rt] = ((sum1[rt] + (LL)len * a % p) + p) % p;
}
}
void pushup(int rt) {
sum1[rt] = (sum1[rt << 1] + sum1[rt << 1 | 1]) % p;
sum2[rt] = (sum2[rt << 1] + sum2[rt << 1 | 1]) % p;
sum3[rt] = (sum3[rt << 1] + sum3[rt << 1 | 1]) % p;
}
// 建线段树,rt为根,l为rt点管辖的左边界, r为rt点管辖的有边界
void build(int rt, int l, int r)
{
lazy_add[rt] = 0;
lazy_mul[rt] = 1;
if(l==r)
{
int temp = w[l];
sum1[rt] = temp;
sum2[rt] = (1ll*sum1[rt] * sum1[rt]) % p;
sum3[rt] = (1ll*sum1[rt] * sum2[rt]) % p;
return ;
}
int mid=(l + r)>>1;
build(rt << 1, l, mid);
build(rt << 1 | 1, mid+1, r);
pushup(rt);
}
// 下传
void pushdown(int rt, int l, int r)
{
int mid = (l + r) >> 1;
solve(rt << 1, mid - l + 1, lazy_add[rt], lazy_mul[rt]);
solve(rt << 1 | 1, r - mid, lazy_add[rt], lazy_mul[rt]);
lazy_add[rt] = 0;
lazy_mul[rt] = 1;
}
// rt为根,l为rt点管辖的左边界, r为rt点管辖的有边界, L为需要修改的左区间,R为需要修改的右区间
void modify(int rt, int l, int r, int L, int R, int a,int b)
{
if(L <= l && r <= R)
{
solve(rt, r - l + 1, a, b);
return ;
}
pushdown(rt, l, r);
int mid = (l + r)>>1;
if(L <= mid) modify(rt << 1, l, mid, L, R, a,b);
if(mid < R) modify(rt << 1 | 1, mid + 1, r, L, R, a,b);
pushup(rt);
}
// rt为根,l为rt点管辖的左边界, r为rt点管辖的有边界, L为需要查询的左区间,R为查询的右区间
int query(int rt, int l, int r, int L, int R)
{
if(L <= l && r <= R)
{
return sum3[rt];
}
pushdown(rt, l, r);
int mid = (l + r)>>1;
int ans = 0;
if(L <= mid) ans += query(rt << 1, l, mid, L, R), ans %= p;
if(mid < R) ans += query(rt << 1 | 1, mid + 1, r, L, R), ans %= p;
pushup(rt);
return ans;
}
// 求树从 x 到 y 结点最短路径上所有节点的值之和
int path_query_Tree(int x, int y)
{
//两点间的修改
int ans = 0;
while(top[x] != top[y]) // 把x点和y点整到一条重链上
{
if(dep[top[x]] < dep[top[y]]) swap(x, y); // 让x点为深的那个点
ans += query(1, 1, n, dfn[top[x]], dfn[x]);
ans %= p;
x = fa[top[x]]; // x每次跳一条链
}
if(dep[x] > dep[y]) swap(x, y); // 让x成为深度更浅的那个点
ans += query(1, 1, n, dfn[x], dfn[y]);
return ans % p;
}
// 树上修改 a为add ,b为mul
void path_modify_Tree(int x, int y, int a,int b)
{
//树上两点距离
while(top[x] != top[y]) // 把x点和y点整到一条重链上
{
if(dep[top[x]] < dep[top[y]]) swap(x, y); // 让x成为对应的头部深度更大的那个点
modify(1, 1, n, dfn[top[x]], dfn[x], a,b); // 累加x的所有子树和
x = fa[top[x]]; // x跳到原来x的头部的父节点
}
if(dep[x] > dep[y]) swap(x, y); // 让x成为深度更浅的那个点
modify(1, 1, n, dfn[x], dfn[y], a,b);
}
int main()
{
scanf("%d", &t);
while(t--){
scanf("%d", &n);
cases++;
// 读入边,建树
for(int i=1; i<=n; ++i) mp[i].clear(),wson[i]=0;
//memset(h, -1, sizeof h);
num = 0;
for(int i=1, x, y; i<n; i++)
{
scanf("%d%d", &x, &y);
mp[x].push_back(y);
mp[y].push_back(x);
}
for(int i=1; i<=n; i++) scanf("%d", &a[i]); // 读入每个点的权值
// 两次dfs把树按照重链剖分
dfs1(1); // 得到sz, fa, dep, wson数组
dfs2(1, 1); // 得到dfn, top数组
build(1, 1, n);
scanf("%d", &m);
printf("Case #%d:
", cases);
// m次询问
for(int i=1, op, x, y, z; i<=m; i++)
{
scanf("%d", &op);
if(op == 1)
{
scanf("%d%d%d", &x, &y, &z);
path_modify_Tree(x, y, z,0); // 将树从 x到 y 结点最短路径上所有节点的值赋值为z
}
else if(op == 2)
{
scanf("%d%d%d", &x, &y, &z);
path_modify_Tree(x, y, z,1); // 将树从 x到 y 结点最短路径上所有节点的值加上z
}
else if(op == 3)
{
scanf("%d%d%d", &x, &y, &z);
path_modify_Tree(x, y, 0,z); // 将树从 x到 y 结点最短路径上所有节点的值乘上z
}
else
{
scanf("%d%d", &x,&y);
printf("%d
", path_query_Tree(x,y));
}
}
}
return 0;
}
G Play the game SET
H Tree Partition
题意:
给出一棵树,切k-1刀分成k棵树,问这k棵树里最大的权值的最小值是多少,其中权值是整棵树上点的权值之和
思路:
对于最小的最大值这种类型的问题,应该很快想到二分法,那么怎么判断当前的mid是否是答案呢?可以从叶子节点开始搜索,如果当前节点及其子树的权值大于mid,则切去最大的子树,最后判断生成多少个子树,如果小于k,则代表当前二分到的mid值大了,应该减小,反之mid应该增加
#include<bits/stdc++.h>
using namespace std;
const int N = 2e5 + 5;
typedef long long LL;
int n, k, t,cnt,cases=0;
LL w[N], mid, wt[N];
vector<int> mp[N];
vector<LL>son[N];
void dfs(int now,int pre){
int sonnum = 0;
wt[now] = w[now];
son[now].clear();
for (int i = 0; i < mp[now].size();i++){
int next = mp[now][i];
if(next==pre)
continue;
dfs(next,now);
son[now].push_back( wt[next]);
sonnum++;
wt[now] += wt[next];
}
sort(son[now].begin(), son[now].end());
while(wt[now]> mid&&(sonnum>=1)){
wt[now] -= son[now][sonnum - 1];
sonnum--;
cnt++;
}
}
int main(){
cin >> t;
while(t--){
cin >> n>>k;
cases++;
for (int i = 1; i <= n;i++){
mp[i].clear();
}
for (int i = 0; i < n - 1; i++)
{
int a, b;
cin >> a >> b;
mp[a].push_back(b);
mp[b].push_back(a);
}
LL l = 0, r = 0;
for (int i = 1; i <= n;i++){
cin >> w[i];
l = max(l, w[i]);
r += w[i];
}
while(l<r){ //二分找符合条件的最小值
mid = (l + r) >> 1;
cnt = 1;
dfs(1,0);
if(cnt<=k)
r = mid;
else
l = mid + 1;
}
printf("Case #%d: %lld
", cases, l);
}
return 0;
}
I Portal
J Bob's Poor Math
K Color Graph
题意:
一个n个点m条边的无向图,给图中的边染色,不允许存在所有边被染色的奇环,求最大的染色数。
思路:
一个无奇环的图必定是一个二分图,所以只需要找一个最大边数的二分图就行了。
由于数据量很小,可以直接二进制枚举每个点属于二分图的哪一部分,然后将两个端点分属两个部分的边加起来即可
#include<bits/stdc++.h>
using namespace std;
const int N = 1e6 + 5;
int t, n, m,color[20],res=0,k;
struct node{
int u, v;
} edge[200];
int main(){
cin>>t;
while(t--){
k++;
res = 0;
cin >> n >> m;
for (int i = 0; i < m;i++){
cin >> edge[i].u >> edge[i].v;
}
for (int i = 0; i < (1 << n);i++){
for (int j = 0; j < n;j++){
if(i&(1<<j)){
color[j] = 1;
}
else{
color[j] = 0;
}
}
int sum = 0;
for (int j = 0; j < m;j++){
int u=edge[j].u-1;
int v = edge[j].v-1;
if(color[u]!=color[v]){
sum++;
}
}
res = max(res, sum);
}
printf("Case #%d: %d
", k,res);
}
return 0;
}