本周主要学习了搜索算法,bfs,dfs,还学到了如何遍历容器,最小生成树,进制哈希算法,遇到了之前学过的快速幂算法,下周开始学习动态规划、多做题,将所学过的能灵活运用出来。
题目:小C的周末(auto遍历容器
解析:
本道题题意是说每次接一根线,看加到哪根线一个游戏就可以开始玩了,如果只有一个人玩该游戏就输出0,不存在就输出-1,否则就输出接到第几根线该种类游戏就可以开始了。这道题用并查集将每一个点连接起来,同时每次连接结束就都需要检查,是否有游戏可以开始玩了,我们拿点集(初始都是自己要玩的游戏)来记录每种游戏连接了的人数,每次添加一条网线时,就将小点集放入大点集中(这样可以减少转移耗费的时间)(同时判断该删除的点集中是否有可以开始的游戏),删除小点集。结束所有添加线后,最后按游戏编号顺序输出。
遍历小点集时,使用for(auto j : ma[v] ) 只会将容器中有的遍历一遍,不会从1~m游戏全部遍历一遍,大大减少了时间的损耗。(注意一下这样的写法,之前没有写过)。
代码:
#include <bits/stdc++.h>
using namespace std;
#define int long long
#define pii pair<int, int>
int n,m,q;
int f[100005];
int ans[100005];
map<int,int>ma[100005];
int cnt[100005];
int find(int x){
if(f[x]==x) return x;
return f[x]=find(f[x]);
}
signed main()
{
ios::sync_with_stdio(0),cin.tie(0);
while(cin>>n>>m>>q){
for(int i=1;i<=n;i++){
ma[i].clear();f[i]=i;
ans[i]=-1;//答案记录数组初始值为-1
}
memset(cnt,0,sizeof(cnt));
for(int i=1;i<=n;i++){
int x;
cin>>x;
cnt[x]++;
ma[i][x]++;//自己以自己作为集群
}
int u,v;
for(int k=0;k<q;k++){
cin>>u>>v;
u=find(u),v=find(v);
if(ma[u].size()<ma[v].size()) swap(u,v);
f[v]=u;
for(auto j:ma[v] ){
ma[u][j.first]+=j.second;//当前游戏种类j.first的人数相加,mp[x][j.first]+mp[v][j.first]
//ans答案的统计
if(ma[u][j.first]==cnt[j.first]&&ans[j.first]==-1)
ans[j.first]=k+1;//当前添加网线的编号
}
ma[v].clear();//小点集删除
}
for(int i=1;i<=m;i++){
if(cnt[i]==1){
cout<<"0"<<endl;
}
else if(ans[i]!=0){
cout<<ans[i]<<endl;
}
else{
cout<<"-1"<<endl;
}
}
}
return 0;
}
题目:寻找道路
解析:
本道题题意是找到从起点到终点最短的路径,同时还要满足经过的所有的点的出边都要直接或间接与终点连通,找最短路径用bfs遍历即可,但是每个点的出边指向的点就需要特殊处理,需要使用·两次bfs,首先反向建图将从终点开始能达到的点标记,然后再遍历每个点所连接的点,只要他连接的点到达不了终点,也标记一下,最后遍历没有标记过的点找到最短路径。
代码:
#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define pii pair<int, int>
int n,m;
int X,Y;
int ans[10004];
bool vis[10004];
bool vi[10004];
queue<int> q;
signed main(){
ios::sync_with_stdio(0),cin.tie(0);
cin>>n>>m;
vector<int> vf[n+1];
for(int i=0;i<m;i++){
int x,y;
cin>>x>>y;
vf[y].push_back(x);
}
cin>>X>>Y;
q.push(Y);
vis[Y]=1;
while(!q.empty()){
int c=q.front();
q.pop();
for(auto i : vf[c]){
if(vis[i]) continue;
vis[i]=1;
q.push(i);
}
}
memcpy(vi,vis,sizeof(vis));
for(int i=1;i<=n;i++){
if(!vi[i]){//此点没有指向终点,指向它的点也不能选;
for(auto j : vf[i]){
vis[j]=0;
}
}
}
memset(ans,-1,sizeof(ans));
q.push(Y);
ans[Y]=0;
while(!q.empty()){
int c=q.front();
q.pop();
for(auto i : vf[c]){
if(!vis[i]||ans[i]!=-1) continue;
vis[i]=-1;
q.push(i);
ans[i]=ans[c]+1;
}
}
cout<<ans[X]<<endl;
return 0;
}
题目:lxy的通风报信(bfs,最小生成树)7.24
G-lxy的通风报信_河南萌新联赛2024第(二)场:南阳理工学院 (nowcoder.com)
解析:
本题算是最小生成树的模板,做这种题首先就是建图,然后再对图进行操作。这道题解法是将友军之间相互距离全部算出来,最后使用最小生成树算法找到最小代价。
代码:
#include <bits/stdc++.h>
using namespace std;
#define int long long
#define pii pair<int, int>
int n,m;
struct pp{
int u;
int v;
int l;
};
int f[100];
char a[1003][1003];
int dx[]={0,0,-1,1},dy[]={-1,1,0,0};
map<pii,int> ma;
vector<pp> xd;
void bfs(int x,int y){
int jl[1003][1003]={0};
queue<pii> q;
q.push({x,y});
int bh=ma[{x,y}];
jl[x][y]=0;
while(!q.empty()){
int x1=q.front().first,y1=q.front().second;
q.pop();
for(int i=0;i<4;i++){
int xx=dx[i]+x1,yy=dy[i]+y1;
if(xx<=0||xx>n||yy<=0||yy>m) continue;
if(a[xx][yy]=='#'||jl[xx][yy]!=0) continue;
q.push({xx,yy});
jl[xx][yy]=jl[x1][y1]+1;
if(a[xx][yy]=='*'){
xd.push_back({bh,ma[{xx,yy}],jl[xx][yy]});
}
}
}
}
bool cmp(pp x,pp y){
return x.l<y.l;
}
int find(int x){
if(f[x]==x) return x;
return f[x]=find(f[x]);
}
signed main(){
std::ios::sync_with_stdio(0);
std::cin.tie(0);
cin>>n>>m;
int t=1;//给我军编号
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++) {
cin>>a[i][j];
if(a[i][j]=='*'){
ma[{i,j}]=t;
t++;
}
}
}
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
if(a[i][j]=='*'){
bfs(i,j);
}
}
}
t--;
sort(xd.begin(),xd.end(),cmp);
for(int i=1;i<=55;i++) f[i]=i;
int ans=0;
for(int i=0;i<xd.size();i++){
int x=find(xd[i].u),y=find(xd[i].v);
if(x==y) continue;
t--;
f[x]=y;
ans+=xd[i].l;
}
if(t>1){//至少有一个被完全包围
cout<<"No"<<endl;
return 0;
}
cout<<ans<<endl;
return 0;
}
题目:A*BBBB(前缀和)
D-A*BBBB_河南萌新联赛2024第(二)场:南阳理工学院 (nowcoder.com)
解析:
求a*b;但a,b的值很大不能直接乘,需要转化为字符串来做,因为b每个位数相同,所以ai中与b单个字符相乘可以得到的数会从持续到从哪到哪lb(b字符串的长度)的长度,前缀和记录最后再遍历一遍前缀和,得到字符串,具体操作详见代码
代码:
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define pii pair<int,int>
void solve(){
string a,b;
cin>>a>>b;
char b1=b[0];
int bs=b[0]-'0';
if(a=="0"||bs==0){
cout<<"0"<<endl;
return ;
}
int num[5000006]={0};
int lb=b.size(),la=a.size();
int da=lb+la;
for(int i=la-1,j=1;i>=0;i--,j++){
int as=a[i]-'0';
int bj=as*bs;
int xb=j;
while(bj>0){
int y=bj%10;
num[xb]+=y;
num[xb+lb]-=y;
bj/=10;
xb++;
}
}
int sum=0;
int sum1=0;
char s[5000006];
for(int i=1;i<=da;i++){
sum+=num[i];
sum1+=sum;
int y=sum1%10;
char c=y+'0';
sum1/=10;
s[i]=c;
}
int lg=0;
for(int i=da;i>0;i--){
if(lg==0&&s[i]=='0') continue;
else {
lg=1;
}
cout<<s[i];
}
cout<<endl;
}
signed main(){
std::ios::sync_with_stdio(0);
std::cin.tie(0);
int T;
cin>>T;
while(T--){
solve();
}
return 0;
}
题目:“好”字符(进制哈希)
E-“好”字符_河南萌新联赛2024第(二)场:南阳理工学院 (nowcoder.com)
解析:
本题题意是求好字符,好字符的定义是:一个字符在字符串a中,在所有b的循环同构中所有a,b对应位置,如果一个字符在每个对应位置a,b要么全是此字符,要么全不是此字符。我们可以使用进制哈希来做,对每个存在的字符x当作1其余字符当作0,a字符串变为2*n的长度,从1开始到n,每次取n的长度,就相当于b每次的不同循环同构,用值来确定所有x字符出现的位置是否相同,有相同答案就加1;
代码:
#include <bits/stdc++.h>
using namespace std;
#define int long long
#define pii pair<int, int>
#define ull unsigned long long
int n;
ull P=131;
ull h1[2000006],h2[1000006],p[2000006]={1};//h记录到每一位它的哈希值
void hx(string s,char a,ull *h){
for(int i=1;i<s.size();i++){
h[i]=P*h[i-1]+(s[i]==a);//相同为1;
}
}
ull pd(ull l,ull r,ull *h){
return h[r]-h[l-1]*p[r-l+1];
}
signed main(){
std::ios::sync_with_stdio(0);
std::cin.tie(0);
cin>>n;
string a,b;
cin>>a>>b;
set<char> se(a.begin(),a.end());
a=' '+a+a;
b=' '+b;
int ans=0;
for(int i=1;i<=2*n;i++) p[i]=P*p[i-1];
for(auto x : se){
hx(a,x,h1);
hx(b,x,h2);
for(int i=1;i<=n;i++){
if(pd(1,n,h2)==pd(i,i+n-1,h1)) {//截取n的长度,用哈希值判断他们是否相等
ans++;
break;
}
}
}
cout<<ans<<endl;
return 0;
}
题目:数独挑战
G-数独挑战_第二周算法入门班第六章习题:搜索与搜索剪枝 (nowcoder.com)
解析:
本题要对每行每列,以及每9个为一块,里面的数字都不相同,我们拿set记录是否出现,vector记录需要填数字的点,然后再对每个区域进行dfs遍历每一个可能出现的数,题目一定有解,所以所有点全部确立好了就退出遍历。
代码:
#include <bits/stdc++.h>
using namespace std;
#define int long long
#define pii pair<int, int>
#define ull unsigned long long
set<int> mx[10];
set<int> my[10];
set<int> zg[3][3];
vector<pii> v;
int a[10][10];
int n;
int lg=0;
void dfs(int t){
if(t>=v.size()) {
lg=1;
return;
}
int x=v[t].first,y=v[t].second;
for(int i=1;i<10;i++){
if(mx[x].count(i)||my[y].count(i)||zg[x/3][y/3].count(i)) continue;
mx[x].insert(i),my[y].insert(i),zg[x/3][y/3].insert(i);
a[x][y]=i;//cout<<t<<"?";
dfs(t+1);
if(lg) return;
a[x][y]=0;
mx[x].erase(i),my[y].erase(i),zg[x/3][y/3].erase(i);
}
return;
}
signed main(){
std::ios::sync_with_stdio(0);
std::cin.tie(0);
n=9;
for(int i=0;i<n;i++){
for(int j=0;j<n;j++) {
cin>>a[i][j];
if(a[i][j]!=0){
mx[i].insert(a[i][j]);
my[j].insert(a[i][j]);
zg[i/3][j/3].insert(a[i][j]);
}
else{
v.push_back({i,j});
}
}
}
dfs(0);
for(int i=0;i<n;i++){
for(int j=0;j<n;j++){
cout<<a[i][j];
if(j!=n) cout<<" ";
}
cout<<endl;
}
return 0;
}
题目:[NOIP2010]关押罪犯(贪心,并查集
J-[NOIP2010]关押罪犯_第二周算法入门班第六章习题:搜索与搜索剪枝 (nowcoder.com)
解析:
本题题意是给两个房子分配人,使他们存在的最大怨恨值最小,如何分配,思路就是贪心将怨气大的两个人先分到不同的房子,遇到不得不让有怨恨值的人待在一起,这时怨恨值就是答案。
那么我们如何判断不得不将a,b他们放在一起呢?其实就是a,b之前有相同的怨恨目标x,并且和x的怨恨值更大。如何判断有相同怨恨目标呢?我们需要使用并查集,每次将a的敌人放在一起,b的敌人放在一起,当在后面敌人之间也有小怨恨值,敌人之间就不得不在一起了;
代码:
#include <bits/stdc++.h>
using namespace std;
#define int long long
#define pii pair<int, int>
#define ull unsigned long long
int f[20004];
int dr[20004];
struct yh{
int x;
int y;
int li;
};
bool cmp(yh x,yh y){
return x.li>y.li;
}
int find(int x){
if(f[x]==x) return x;
return f[x]=find(f[x]);
}
void solve(){
int n,m;
cin>>n>>m;
vector<yh> v;
for(int i=0;i<m;i++){
int x,y,z;
cin>>x>>y>>z;
v.push_back({x,y,z});
}
for(int i=1;i<=n;i++) f[i]=i;
sort(v.begin(),v.end(),cmp);
int ans=0;
for(int i=0;i<v.size();i++){
int x=v[i].x,y=v[i].y;
int xx=find(x),yy=find(y);
if(xx==yy){//如果之前有共同的敌人,但现在他们又是敌人了,不得不呆在一起,因为他们与之前共同的敌人怨气更大;
cout<<v[i].li<<endl;
return;
}
else{
if(!dr[x]){//x没有敌人
dr[x]=y;
}
else{//将x的敌人和y连起来(敌人的敌人就是朋友
int a=find(dr[x]);//将他们连接
if(a!=yy) f[yy]=a;
}
if(!dr[y]){
dr[y]=x;
}
else{
int b=find(dr[y]);
if(b!=xx) f[xx]=b;
}
}
}
cout<<"0"<<endl;
return;
}
signed main(){
std::ios::sync_with_stdio(0);
std::cin.tie(0);
int t;
t=1;
//cin>>t;
while(t--){
solve();
}
return 0;
}
题目:Ubiquity
解析:
这道题原本以为不能使用搜索,但是发现它每个袋中球的和的乘积不超过10e5,所以可以将每个袋子的一个球枚举看乘积是否为x,这里没有规定有多少个袋子,所以我们可以将vector容器装一个袋子中的球,再用vector容器嵌套vector,作为袋子,搜索时就搜索每一个袋子。
代码:
#include <bits/stdc++.h>
using namespace std;
#define int long long
#define pii pair<int, int>
#define ull unsigned long long
const int mod=1e9+7;
int n,x;
int len;
int ans=0;
vector<vector<int> > v;
void dfs(int t,int sum){
if(t>=len){
if(sum==x) ans++;
return;
}
for(auto i : v[t]){
int y=sum*i;
//cout<<len<<endl;
if(y>x) continue;
dfs(t+1,y);
}
}
signed main(){
std::ios::sync_with_stdio(0);
std::cin.tie(0);
cin>>n>>x;
for(int i=0;i<n;i++){
int l;
vector<int> v1;
cin>>l;
for(int i=0;i<l;i++){
int y;
cin>>y;
v1.push_back(y);
}
v.push_back(v1);
}
len=v.size();
dfs(0,1);
cout<<ans<<endl;
return 0;
}
题目:FG operation(容斥定理,快速幂
解析:
一个数学问题,只要序列所有数在0到9,而且必须要有一个0,和1个9,问这种序列有多少种,根据容斥定理,对n个数全排列,为10的n次,没有9的排列有9的n次,没有0的排列也有9的n次,但没有9的排列也可能没有0,没有0的排列也可能没有9,所以我们多减了一次即没有9,也没有0的排列,给其加上得到的就是答案。
代码:
#include <bits/stdc++.h>
using namespace std;
#define int long long
#define pii pair<int, int>
#define ull unsigned long long
int const mod=1e9+7;
int ksm(int x,int a){//a的x次
int res=1;
while(x){//将x次二进制向右移,看末尾0,1;
if((x&1)) res=(res*a)%mod;
a=(a*a)%mod;
x>>=1;
}
return res;
}
signed main(){
std::ios::sync_with_stdio(0);
std::cin.tie(0);
int n;
cin>>n;
int q1=ksm(n,10)%mod;
int q2=ksm(n,9)*2%mod;//0没选或9没选
int q3=ksm(n,8)%mod;//0和9都没有选的情况
int ans=(mod+q1-q2+q3)%mod;
cout<<ans<<endl;
return 0;
}