目录
问题 A: 符文宗师的魔方阵
输入的时候记录一下每行每列有多少个-1,行数>-1的个数,可以遍历一遍,找到没有-1的那行计算出矩阵每行或每列的和,之后遍历一遍数组计算即可。
int tx[10],ty[10];//记录每行每列-1的个数
void solve()
{
int a[10][10];
for(int i=1;i<=5;i++){
for(int j=1;j<=5;j++){
cin>>a[i][j];
if(a[i][j]==-1) tx[i]++,ty[j]++;
}
}
int sum = 0;
for(int i=1;i<=5;i++){
if(tx[i]==0){
for(int k=1;k<=5;k++) sum+=a[i][k];
break;
}
}
//cout<<sum<<endl;
for(int i=1;i<=5;i++){
for(int j=1;j<=5;j++){
if(a[i][j]==-1){
int temp;
if(tx[i]==1){
temp = 0;
for(int k=1;k<=5;k++){
if(a[i][k]!=-1) temp+=a[i][k];
}
a[i][j] = sum-temp;
tx[i]--,ty[j]--;
}else if(ty[j]==1){
temp = 0;
for(int k=1;k<=5;k++){
if(a[k][j]!=-1) temp+=a[k][j];
}
a[i][j] = sum-temp;
tx[i]--,ty[j]--;
}
}
}
for(int j=5;j>=1;j--){
if(a[i][j]==-1){
int temp;
if(tx[i]==1){
temp = 0;
for(int k=1;k<=5;k++){
if(a[i][k]!=-1) temp+=a[i][k];
}
a[i][j] = sum-temp;
tx[i]--,ty[j]--;
}else if(ty[j]==1){
temp = 0;
for(int k=1;k<=5;k++){
if(a[k][j]!=-1) temp+=a[k][j];
}
a[i][j] = sum-temp;
tx[i]--,ty[j]--;
}
}
}
}
for(int i=1;i<=5;i++){
for(int j=1;j<=5;j++) printf("%3d ",a[i][j]);
puts("");
}
}
问题 B: APP的成绩单
利用结构体实现,根据学生每个信息的不同特征可以实现分类
typedef struct student{
string name;
string num;
string gender;
string goal;
}student;
void solve()
{
int t;
cin>>t;
student s[100+10];
for(int i=0;i<t;i++){
string a[4];
cin>>a[0]>>a[1]>>a[2]>>a[3];
for(int j=0;j<4;j++){
if(a[j]=="boy"||a[j]=="girl") s[i].gender = a[j];
else if( (a[j][0]>='a' && a[j][0]<='z') ||(a[j][0]>='A' && a[j][0]<='Z')){
s[i].name = a[j];
}else if(a[j].length()==10) s[i].num = a[j];
else s[i].goal = a[j];
}
}
for(int j=0;j<t;j++){
cout<<s[j].name<<" "<<s[j].num<<" "<<s[j].gender<<" "<<s[j].goal<<endl;
}
}
问题 C: 6.1.4.1 最大的节点
题目描述容易想到用dfs去处理,但是这题数据量为1e5,如果从节点1~n依次搜索一遍,时间复杂度会达到O(n^2)。这题处理运用到了正难则反的思想,通过反向建立有向图,从节点下标最大的点开始搜索,找到哪些点可达,那么这么可达的点的最大标号就是搜索的起点。注意,在dfs中是不需要回溯的,这里推荐一下lxr学长讲dfs回溯的一篇文章,里面画的搜索树可以帮助理解:https://blog.csdn.net/m0_61735576/article/details/127814886?spm=1001.2014.3001.5502
我们反向建图从下标大的点搜索相当于搜索树从叶子往根部搜索,是不同分支合并到一条路的过程,是不用考虑选和不选的问题的。
const int N = 100000+10;
int idx,n,m,maxn;
int h[N],e[N],ne[N];
bool vis[N];
int res[N];
void add(int a,int b){
e[idx] = b,ne[idx] = h[a],h[a] = idx++;
}
void dfs(int u)
{
if(res[u]==0) res[u] = maxn;
for(int i=h[u];i!=-1;i=ne[i]){
int j = e[i];
if(!res[j]) dfs(j);
}
}
void solve()
{
cin>>n>>m;
memset(h,-1,sizeof h);
while(m--){
int a,b;
scanf("%lld %lld",&a,&b);
add(b,a);
}
for(int i=n;i>=1;i--){
//memset(vis,0,sizeof vis);
if(!res[i]){
maxn = i;
dfs(i);
}
}
for(int i=1;i<=n;i++) printf("%lld ",res[i]);
}
问题 D: 6.2.2.1 油田
这题还没明白为什么读入scanf("%s")会WA
迷宫问题用dfs和bfs搜索都可以,这题是典型的一类迷宫问题有固定的套路。存储完图后进行遍历,如果该点没被搜索过,就开始进行dfs,把所有和该点连通的点都标记完后,联通块数量+1。
int n,m;
char g[100+10][100+10];//存储图
bool vis[100+10][100+10];//标记是否访问过
PII dxy[8] = {{1,0},{-1,0},{0,-1},{0,1},{-1,-1},{-1,1},{1,-1},{1,1}};//八个方向
bool check(int x,int y)//检查是否走出地图
{
if(x>=1 && x<=n && y>=1 && y<=m) return true;
return false;
}
void dfs(int x,int y)
{
vis[x][y] = true;
for(int i=0;i<8;i++){
int tx = x + dxy[i].first;
int ty = y + dxy[i].second;
if(check(tx,ty) && !vis[tx][ty] && g[tx][ty]=='@'){
dfs(tx,ty);
}
}
}
void solve()
{
while(cin>>n>>m){
if(n==0 && m==0) break;
memset(vis,0,sizeof vis);
int ans = 0;
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++) cin>>g[i][j];
}
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
if(g[i][j]=='@' &&!vis[i][j]){
dfs(i,j);
ans++;
}
}
}
cout<<ans<<endl;
}
}
问题 E: 6.3.2.1 电话网络
题目中描述的关键点其实就是我们离散数学中学到的割点,割点在书中的定义:在一个无向图中,如果有一个顶点集合,删除这个顶点集合以及这个集合中所有顶点相关联的边以后,图的连通分量增多,那么这个点集称为割点集。求关键点的个数就是求割点的个数。
处理有向图的强连通分量通常使用Tarjan算法,每个强连通分量作为搜索树中的一棵子树,搜索时,把当前搜索树中未处理的节点加入栈,回溯时可以判断栈顶到栈中的节点是否为一个强连通分量。
关于Tarjan求割点的算法模板,这里有相关的讲解:
割点(tarjan算法)_axtices的博客-CSDN博客_割点
const int maxn = 100+10;
int n,cnt,root;
int head[maxn] , low[maxn],dfn[maxn],num;
bool cut[maxn];
struct Edge{
int to , next;
}e[maxn*maxn];
void add(int u,int v){
e[++cnt].next = head[u];
e[cnt].to = v;
head[u] = cnt;
}
void tarjan(int u){
dfn[u] = low[u] = ++ num;
int flag = 0;
for(int i = head[u] ; i ; i = e[i].next){
int v = e[i].to;
if(!dfn[v]){
tarjan(v);
low[u] = min(low[u] , low[v]);
if(low[v] >= dfn[u]){
flag ++;
if(u != root || flag > 1){
cut[u] = true;
}
}
}else{
low[u] = min(low[u] , dfn[v]);
}
}
}
void init(){
memset(head,0,sizeof head);
memset(low,0,sizeof low);
memset(dfn,0,sizeof dfn);
memset(cut,false,sizeof cut);
cnt = num = 0;
}
void solve()
{
while(cin >> n && n){
init();
int u,v;
while(cin >> u && u){
while(1){
char c = getchar();
if(c == '\n') break;
cin >> v;
add(u,v);
add(v,u);
}
}
for(int i = 1;i <= n ; i++){
if(!dfn[i]){
root = i;
tarjan(i);
}
}
int ans = 0;
for(int i=1 ; i<= n ; i++){
if(cut[i]) ans ++;
}
cout << ans << endl;
}
}
问题 F: 7.1.4.1 重型运输
题意为在无向图中,要求找到一条起点为1,终点为n且最小边权最大的通路,该通路的最小边权即最大承重。(通路的最小边权值即使构成通路所有边中权重最小的权值)
平常一般用dijkstra算法去求单源点最短路,这里可以通过改造dijkstra中的松弛不等式来解决题中问题。dist数组:记录从点1到当前点,允许通过的最大吨数。dits数组每次查找最大值进行比较,当有道路的最小值都比dist中的值大时,更新dist
网上还看见了用最大生成树来解决这道题的,具体链接:
POJ 1797 Heavy Transportation (最大生成树) - wuli涛涛 - 博客园
const int N = 1010;
int n,m;
int g[N][N];
bool vis[N];
int dist[N];//记录从1到当前点,允许通过的最大吨数
int dijkstra()
{
for(int i=1;i<=n;i++) dist[i] = g[1][i];
dist[1] = 0;
for(int i=0;i<n;i++)
{
int t = -1;
int maxn = -1;
for(int j=1;j<=n;j++){
if(!vis[j] && dist[j]>maxn ){
maxn = dist[j];
t = j;
}
}
for(int j=1;j<=n;j++){
if(dist[j]<min(dist[t],g[t][j]))
dist[j] = min(dist[t],g[t][j]);
}
vis[t] = true;
}
if(dist[n]==0x3f3f3f3f) return -1;
return dist[n];
}
void solve()
{
int t;
cin>>t;
for(int i=1;i<=t;i++){
cin>>n>>m;
memset(g,-1,sizeof g);
memset(vis,0,sizeof vis);
while(m--){
int a,b,c;
cin>>a>>b>>c;
g[a][b] = g[b][a] = max(g[a][b],c);
}
cout<<"Scenario #"<<i<<":"<<endl;
cout<<dijkstra()<<endl<<endl;
}
}
问题 G: 打怪兽version-3
暴力解决即可,从大到小排序,前k个元素不用计入总和
int a[200000+10];
bool cmp(int x,int y){
return x>y;
}
void solve()
{
int res = 0;
int n,k;
cin>>n>>k;
for(int i=0;i<n;i++){
cin>>a[i];
}
sort(a,a+n,cmp);
for(int i=k;i<n;i++) res+=a[i];
cout<<res;
}
问题 H: 2.4.1 间谍
在B个字符串中,找到在A中并且不在C中,按照先进先出原则输出
void solve()
{
int a,b,c;
while(cin>>a>>b>>c)
{
string s;
queue<string>q;
map<string,int>mp;
map<string,int>mp1;
for(int i=0;i<a;i++){
cin>>s;
mp1[s] = 1;
}
for(int i=0;i<b;i++){
cin>>s;
q.push(s);
}
for(int i=0;i<c;i++){
cin>>s;
mp[s] = 1;
}
bool flag = false;
while(q.size()){
auto t = q.front();
q.pop();
if(!mp[t] && mp1[t]){
cout<<t<<" ";
flag = true;
}
}
if(!flag) cout<<"No enemy spy";
cout<<endl;
}
}
问题 I: 2.4.2 Web导航
可以直接用STL里的stack,也可以自己用string数组模拟
string v[100+10];
int hh,tt;//hh为栈顶,0为栈底
void solve()
{
string s;
v[0] = "***###.acm.org/";
while(cin>>s){
if(s[0]=='Q') break;
if(s[0]=='V'){
string str;
cin>>str;
v[++tt] = str;
hh = tt;
cout<<v[tt]<<endl;
}else if(s[0]=='B'){
if(tt<=0){
cout<<"Ignored"<<endl;
}else{
cout<<v[--tt]<<endl;
}
}else if(s[0]=='F'){
if((tt+1)>hh){
cout<<"Ignored"<<endl;
}else{
cout<<v[++tt]<<endl;
}
}
}
}
问题 J: 2.4.3 骑士移动
最短步数考虑用bfs解决,走日字和走上下左右有一些区别,一共有八个方向,用队列实现bfs,从起点开始搜索,每次更新当前点能到达的所有点,最短的路径会比其他的路径更早标记,搜索完地图后输出dist[x][y](x,y)为终点坐标,dist数组为当前点距离起点的距离
int n;
PII st,ed;//起点和终点
char g[300+10][300+10];//存储图
int dist[300+10][300+10];//从起点开始到当前位置一共走了几步
PII dxy[8] = {{-2,-1},{-1,-2},{1,-2},{2,-1},{2,1},{1,2},{-1,2},{-2,1}};//八个方向
bool check(int x,int y)//检查是否走出地图
{
if(x>=1 && x<=n && y>=1 && y<=n) return true;
return false;
}
int bfs()
{
memset(dist,-1,sizeof dist);
dist[st.first][st.second] = 0;
queue<PII>q;
q.push({st.first,st.second});
while(!q.empty()){
auto t = q.front();
q.pop();
for(int i=0;i<8;i++){
int tx = t.first + dxy[i].first;
int ty = t.second + dxy[i].second;
if(check(tx,ty) && dist[tx][ty]==-1){//在图内并且没被访问过
q.push({tx,ty});
dist[tx][ty] = dist[t.first][t.second]+1;
}
}
}
return dist[ed.first][ed.second];
}
void solve()
{
int t;
cin>>t;
while(t--){
cin>>n;
cin>>st.first>>st.second;
cin>>ed.first>>ed.second;
st.first++,st.second++,ed.first++,ed.second++;
cout<<bfs()<<endl;
}
}
问题 L: 吃菜
和01背包模板最大的区别是多了一道不受时间限制的菜可以选择。无论最终点菜顺序是什么,我们都可以将某一份菜安排在m时刻来点,那么就可以把背包容量扩充到 m+w[i]-1,问题转化成了总共有m-1的时间的01背包问题。
状态f[i][j]定义:前 i 个物品,背包容量 j 下的最优解(最大价值)
每次决定当前菜选或者不选:
不选:dp[i][j]=dp[i-1][j];
选:dp[i][j]=dp[i-1][j-s[i].w]+s[i].v;
注意:进行状态转移的时候,状态转移方程中:dp[x][y]与dp[x-1][y-w[i]]状态有关,要保证 y-w[i]这个状态是最优的。所以我们应该按w从小到大排一下序。
下面是朴素版没优化的二维版本
const int N = 3000+10;
int n,m;
struct node{
int w;
int v;
}s[3005]; //存储道菜的价值和吃这道菜所耗费的时间
bool cmp(node a,node b)
{
return a.w<b.w;
}
int dp[3005][6005];
void solve()
{
cin>>n>>m;
for(int i=1;i<=n;i++) cin>>s[i].w>>s[i].v;
sort(s+1,s+1+n,cmp);
for(int i=1;i<=n;i++){
for(int j=0;j<s[i].w+m;j++){
dp[i][j]=dp[i-1][j];
if(j>=s[i].w) dp[i][j]=max(dp[i-1][j],dp[i-1][j-s[i].w]+s[i].v);
}
}
int res=0;
for(int i=1;i<=n;i++) res=max(dp[n][m+s[i].w-1],res);
cout<<res<<endl;
}
问题 M: 矩形
自己没什么思路,看了看别人的想法
将每个点 (x,y) 拆成两个点 x 和 y,再将 x 和 y 连一条无向边。
这样原问题就被抽象成了一个图论问题,而且还是个二分图。相应的操作就变成了已知 a—b,a—d,c—b,c—d 中的三条边,然后添加剩余的一条边。
二分图长这个样子:(图中每条边依附的两个顶点都分属于这两个互不相交的子集,两个子集内的顶点不相邻)
对于每个连通分量的答案(其实就是把当前联通分量对应的二分图变成完全二分图所需加的边数),我们可以用 dfs 来求出它在 x 方向上的点数和 y 方向上的点数,求出二者的乘积,就是相应的完全二分图的边数,再减去当前连通分量上的边数即可。
我们可以先计算出所有连通分量所对应的完全二分图的边数之和,再减去已知的边数,从而得到答案。
const int N = 100000+10;
vector<int> to[N*2];
bool vis[N*2];
vector<int>cnt;
void dfs(int u) {
if (vis[u]) return;
vis[u] = true;
cnt[u/N]++;
for (int it : to[u]) dfs(it);
}
void solve()
{
int n;
cin >> n;
for(int i=0;i<n;i++){
int x,y;
cin>>x>>y;
y += N;
to[x].push_back(y);
to[y].push_back(x);
}
ll ans = 0;
for(int i=0;i<N*2;i++){
if (vis[i]) continue;
cnt = vector<int>(2);
dfs(i);
ans += (ll)cnt[0] * cnt[1];
}
ans -= n;
cout<<ans<<endl;
}