A. Exercising Walk
向上向下走和向左向右走其实是同一种问题,故仅讨论向左向右走。
现在有一个起点x,左边界xl,右边界xr,总共要向左走a步,向右走b步。
首先,如果xl==xr,那么显然一步也不能走。
其次,要保证(b-a)<=(xr-x),即向右步数减向左步数(最终向右移动的距离)要小于等于起点到右边界的距离,向左同理。
可以发现,只要满足上述两个条件,总是可以完成任务的,上下方向的移动也用同样方式检测一遍即可。
#pragma GCC optimize(2)
#include <bits/stdc++.h>
#define maxn 5010
using namespace std;
typedef long long ll;
const ll mod=998244353;
int T, a, b, c, d, x, y, xl, xr, yl, yr;
int check(int l, int r, int x, int xl, int xr)
{
if (xl==xr && (l || r)) return 0;
if (xr-x<r-l) return 0;
if (x-xl<l-r) return 0;
return 1;
}
int main()
{
ios::sync_with_stdio(0); cin.tie(0);
cin>>T;
while (T--)
{
cin>>a>>b>>c>>d;
cin>>x>>y>>xl>>yl>>xr>>yr;
int f=1;
if (!check(a, b, x, xl, xr)) f=0;
if (!check(c, d, y, yl, yr)) f=0;
if (f) cout<<"Yes\n";
else cout<<"No\n";
}
return 0;
}
B. Partition
对一个仅包含合数且每个数大小不超过1e3的集合做一个划分,使得划分出的子集合个数不超过11,且每个子集合中任意两个数的gcd不为1.
一个显然的想法就是找到具有相同gcd的数,然后把它们丢到同一个集合里面。
如何保证最终得到的字集合数量少于十一个?可以发现,题目所给数字的范围较少(都<=1e3),因此其至少有一个素因数是小于sqrt(1e3)的,sqrt(1e3)之内的素数又是不超过11个的。
因此,对每个数找出其最小的素因数,把它加入到这个素因数对应的集合即可。
#pragma GCC optimize(2)
#include <bits/stdc++.h>
#define maxn 301000
using namespace std;
typedef long long ll;
const ll mod=998244353;
int n, a[maxn], T, res[maxn];
vector<int> ans[1100];
int main()
{
ios::sync_with_stdio(0); cin.tie(0);
cin>>T;
while (T--)
{
for (int i=2; i*i<=1000; i++)
ans[i].clear();
cin>>n;
for (int i=1; i<=n; i++)
cin>>a[i];
for (int i=1; i<=n; i++)
for (int j=2; j*j<=a[i]; j++)
if (a[i]%j==0)
{
ans[j].push_back(i);
break;
}
int cot=0;
for (int i=2; i*i<=1000; i++)
{
if (!ans[i].size()) continue;
++cot;
for (auto j: ans[i]) res[j]=cot;
}
cout<<cot<<"\n";
for (int i=1; i<=n; i++)
cout<<res[i]<<" ";
cout<<"\n";
}
return 0;
}
C. K-Complete Word
要求更改给定字符串的某几位,使得字符串每k个字符一个循环,且整体是一个回文串,输出最少需要更改的位数。
整体回文对应到每k个字符一个循环是啥效果,可以自己打下草稿(或脑补)体会一下。不论这个k把整个字符串是分成奇数段还是偶数段,其最终结果都是这k个字符也要是一个回文串。
现在题意就简化成了要让字符串每k个字符一循环且这k个字符还形成回文。
如何使的更改的位数尽量的少?可以通过贪心来解决。
只要对每一个颜色相同的位置,贪心计算最终变成哪种字符最优即可。
#pragma GCC optimize(2)
#include <bits/stdc++.h>
#define maxn 201000
using namespace std;
typedef long long ll;
const ll mod=998244353;
int T, n, k, ans, ans2, cot[maxn][30];
string s;
int main()
{
ios::sync_with_stdio(0); cin.tie(0);
cin>>T;
while (T--)
{
ans=0;
cin>>n>>k>>s;
s=" "+s;
for (int i=1; i<=k; i++)
{
for (int j=0; j<=25; j++)
cot[i][j]=0;
for (int j=1; j<=n/k; j++)
cot[i][s[(j-1)*k+i]-'a']++;
}
for (int i=1; i<=k/2; i++)
{
int temp=0;
for (int j=0; j<=25; j++)
temp=max(temp, cot[i][j]+cot[k-i+1][j]);
ans+=2*(n/k)-temp;
}
if (k&1)
{
int temp=0;
for (int j=0; j<=25; j++)
temp=max(temp, cot[k/2+1][j]);
ans+=n/k-temp;
}
cout<<ans<<"\n";
}
return 0;
}
D. Walk on Matrix
初看似乎很难,但是想到关键点后就很简单
不多废话,直接给出答案,可以自行体会其中奥妙(手动滑稽
以下皆为二进制数字
11111(位数要比k多) | 11111 | 10000 |
---|---|---|
11111 | k | 11111 |
10000 | 11111 | k |
#pragma GCC optimize(2)
#include <bits/stdc++.h>
#define maxn 201000
using namespace std;
typedef long long ll;
const ll mod=998244353;
int T, k;
int main()
{
ios::sync_with_stdio(0); cin.tie(0);
cin>>k;
int fu=0, up=1;
for (; up<=k; up*=2)
fu|=up;
fu|=up;
cout<<3<<" "<<3<<"\n";
cout<<fu<<" "<<fu<<" "<<up<<"\n";
cout<<fu<<" "<<k<<" "<<fu<<"\n";
cout<<up<<" "<<fu<<" "<<k<<"\n";
return 0;
}
E. Height All the Same
大力观察题
题目要求等价于能不能通过操作1使得所有位置的奇偶性相同。
可以把所给的网格看成黑白棋盘,操作1就相当于可以选一对异色的位置,让这两个位置的奇偶性都改变。
不难(个鬼)发现,当棋盘中为奇数的位置数有偶数个时可以把整个棋盘用操作1转变成全偶,当棋盘中为偶数的位置数有偶数个事可以把整个棋盘用操作1转变成全奇。
因此当n*m是奇数时,上面两种情况一定有一种发生,也就是一定可行,情况数就是(r-l+1)的n*m次方。
当n*m是偶数时,只要找出为奇数位置数为偶数有多少情况就行了。
用k1表示可选范围内奇数的数量,k2表示偶数的数量。列出式子,发现是二项式(k1+k2)n*m的偶数项,要求出这个只要构造出(k1-k2)n*m,两者相加,然后除二就行了。
(发现看成黑白棋盘好像没啥卵用,可能可以方便观察吧…
#pragma GCC optimize(2)
#include <bits/stdc++.h>
#define maxn 501000
using namespace std;
typedef long long ll;
const ll mod=998244353;
ll ksm(ll a, ll b)
{
ll ans=1;
while (b)
{
if (b&1) ans=ans*a%mod;
b=b>>1, a=a*a%mod;
}
return ans;
}
ll n, m, l, r;
int main()
{
cin>>n>>m>>l>>r;
if (n*m%2==1) cout<<ksm(r-l+1, n*m);
else cout<<(ksm(r-l+1, n*m)+(r-l+1)%2)*ksm(2, mod-2)%mod;
}
F. Independent Set
题意有点难理解
题意翻译:给一棵树,你从中任意挑选出一个非空边集E,E中的每条边都会连接两个点,这些点又形成了一个点集G,在G中再挑出独立集(独立集中任意两点没有边相连,空集也算独立集),问总共能挑出多少独立集(两种不同的边集最后挑出的含有相同点的独立集要算两种)
先说明下,有大佬指出我这个写法状态设计的偏复杂了,是有更优秀简便的写法的(我没想出来,只能就按这个写题解了orz
首先应该要能看出,这是一道树形dp。还是有不少特点指向树形dp的吧。
不妨先考虑一些简单的情况,假设这棵树的所有边都在边集内(也就是所有点都在点集内了)。我们要找的是独立集的数量,而独立集对应到一颗树上的节点结果就是:一个点要是被选中,那么它的子节点一定不能选;一个节点要是没有被选中,那么它的子节点就可以选也可以不选。那么这时候,我们就可以用dp[maxn][2](第一维用来表示是哪个点,第二位用来表示这个点在独立集内或不在)来表示一个点的子树的答案,子节点向父亲节点的转移也是显然的。
现在再加入题目中的条件:树形结构不一定是完整的。有些边可能没有,因此有些点可能也没有。结合题意把这个条件转化一下,以点为中心的说法就是:如果一个点周围的所有边都不在边集内,这个点也不在点集内。且如果父节点和其某个子节点间的边不在边集内,那么它们是可以同时在独立集内的。
现在我们来考虑在这个新的条件下如何树形dp。以下注意区分点集和独立集。
首先对于每个点,至少有两种状态:在点集内和不在点集内,在点集内又可分为在独立集内和不在独立集内。对于在点集内的点,其由子节点转移值的时候又分为两者之间的边在边集内和不在边集内得两种情况。
在原来的基础上加一个dp数组用来记录这个点不在点集内的答案,按照上述几种情况讨论一下再进行转移似乎就可以计算出答案了?
其实还有一些漏洞:判断不出子节点是否真正在点集内,没法得知有多少情况一个点周围的所有边都不在边集内。比如我计算点P在点集内的答案的时候会计算到P与所有子节点都不相连的情况,而计算P的父节点的时候又会计算P在点集内但与父节点不相连的情况,这时候P边上没有边,明明不在点集内,却被计算了进去。
为了解决这个问题,我给dp数组又加了一维变成dp[maxn][2][2],第三维用于表示这个点是否与至少一个子节点存在边。
接下来就是分类讨论然后进行转移了,这块有点复杂感觉写不清楚(主要是懒的写了
可以自己动手推一下,或参考代码,代码里加了不少注释,应该还算易读。(说了这么多,其实代码也不算长,去掉注释也就50行左右
#pragma GCC optimize(2)
#include <bits/stdc++.h>
#define maxn 301000
using namespace std;
typedef long long ll;
const ll mod=998244353;
ll n, dp[maxn][2][2], dp2[maxn];
//dp[i][0/1][0/1]表示i号点在点集内时其子树的独立集种类数
//dp第二维表示这个点是(1)否(0)在独立集内
//第三维表示其是(1)否(0)有和至少一个儿子间存在边
//dp2[i]表示i号点不在点集内时其子树的独立集种类数
vector<int> edge[maxn];
void dfs(int now, int fa)
{
if (now!=1 && edge[now].size()==1)
{
//叶子节点肯定不与任何一个儿子相连(因为没有子节点
dp[now][0][0]=dp[now][1][0]=1;
dp2[now]=1;
return ;
}
dp[now][0][1]=dp[now][1][1]=1;
dp[now][0][0]=dp[now][1][0]=1;
dp2[now]=1;
for (auto to: edge[now])
{
if (to==fa) continue;
dfs(to, now);
//从子节点转移答案时记得要分(边在边集内)和(边不在边集内)讨论,两种都要算进去
//当前点不在独立集内且与至少一个儿子相连的答案
dp[now][0][1]=dp[now][0][1]*((dp2[to]+2*dp[to][0][1]+2*dp[to][1][1]+dp[to][0][0]+dp[to][1][0])%mod)%mod;
//当前点在独立集内且与至少一个儿子相连的答案
dp[now][1][1]=dp[now][1][1]*((dp2[to]+2*dp[to][0][1]+dp[to][1][1]+dp[to][0][0])%mod)%mod;
//当前点不在独立集内且不与任何一个儿子相连的答案
dp[now][0][0]=dp[now][0][0]*(dp2[to]+dp[to][0][1]+dp[to][1][1])%mod;
//当前点在独立集内且不与任何一个儿子相连的答案
dp[now][1][0]=dp[now][1][0]*(dp2[to]+dp[to][0][1]+dp[to][1][1])%mod;
//当前点不在点集内的答案
dp2[now]=dp2[now]*(dp2[to]+dp[to][0][1]+dp[to][1][1])%mod;
}
//记得要去重,因为前面把(不与任何一个相连的情况)也算进了(至少和一个相连的情况)
dp[now][0][1]=(dp[now][0][1]-dp[now][0][0]+mod)%mod;
dp[now][1][1]=(dp[now][1][1]-dp[now][1][0]+mod)%mod;
}
int main()
{
ios::sync_with_stdio(0); cin.tie(0);
cin>>n;
for (int i=1, u, v; i<n; i++)
{
cin>>u>>v;
edge[u].push_back(v);
edge[v].push_back(u);
}
dfs(1, 0);
//要减一是因为dp2里包含了边集全空的一种情况,不合法需要去掉
cout<<(dp[1][0][1]+dp[1][1][1]+dp2[1]-1)%mod;
return 0;
}