A Artwork
输入:
n,m表示原图为n*m个白色方格,输入x1,y1,x2,y2表示将x1,y1,x2,y2涂为黑色。
输出:
对于每个x1,y1,x2,y2输入当前图案白色联通块的数目。
思路:
先维护得到最后图案的形状,包括每个黑色方块的厚度。
对于每个x1,y1,x2,y2倒序访问,将其对应位置的黑色还原为白色。通过上下左右的联通关系,更新并查集得到每组的答案。
#include<bits/stdc++.h> using namespace std; struct sc { int a,b,c,d; } edg[10010]; int p[1100100],g[1010][1010],ans[10010]; int dx[]= {0,1,-1,0}; int dy[]= {1,0,0,-1}; int n,m,q,cnt=0,k=0; int f(int x) { return x==p[x]?x:p[x]=f(p[x]); } void u(int a,int b) { int x=f(a),y=f(b); p[x]=y; } int id(int x,int y) { return x*(m+2)+y+1; } int main() { scanf("%d %d %d",&n,&m,&q); memset(g,0,sizeof g); for(int i=0; i<=n+1; i++) for(int j=0; j<=m+1; j++) { p[id(i,j)]=id(i,j); if(!i||!j|i>n||j>m)g[i][j]=1; } for(int i=0; i<q; i++) { int a,b,c,d; scanf("%d %d %d %d",&a,&b,&c,&d); for(int k=a; k<=c; k++) for(int j=b; j<=d; j++) g[k][j]++; edg[i]= {a,b,c,d}; } for(int i=1; i<=n; i++) for(int j=1; j<=m; j++) if(!g[i][j]) for(int l=0; l<4; l++) if(!g[i+dx[l]][j+dy[l]]) u(id(i+dx[l],j+dy[l]),id(i,j)); for(int i=1; i<=n; i++) for(int j=1; j<=m; j++) if(id(i,j)==f(id(i,j))&&!g[i][j]) cnt++; while(q--) { int a=edg[q].a,b=edg[q].b,c=edg[q].c,d=edg[q].d; ans[k++]=cnt; for(int i=a; i<=c; i++) for(int j=b; j<=d; j++) { g[i][j]--; if(!g[i][j]) { cnt++; for(int l=0; l<4; l++) { int x=i+dx[l],y=j+dy[l]; if(!g[x][y]&&f(id(x,y))!=f(id(i,j))) { u(id(i,j),id(x,y)); cnt--; } } } } } for(int i=k-1; i>=0; i--)printf("%d\n",ans[i]); }
反思:
场上心里闪过并查集的念头,但是只想到了对于黑色进行并查集,没有从一个角度看问题,比如反过来看,看答案的对应面,比如补集,或者倒着获得答案。
数组开小了, 应该算清楚每个数组的范围。
B Bless You Autocorrect!
输入:
n,m表示下面有n,m个串,n表示手机里的字典词按照出现的顺序表示优先级,m表示你想输入的单词。
输出:
对于每个你想输入的单词,输出最小打出它需要的按键次数。
思路:
用字典树来存储单词,字典树(树上的每一个串的子串都会有一个id),通过这些id和每个串每打一个按键可以得到的另外一个串建立双向边。
跑一遍bfs即可获得每个串可以打出需要的最小按键数量。
#include<bits/stdc++.h> using namespace std; const int maxn=1e6; char s[maxn]; int tot=0,tree[30][maxn],edg=0,head[maxn],dp[maxn],cnt[maxn],vis[maxn]; struct sc { int to,nxt; }p[6*maxn]; void add(int a,int b) { p[edg]={b,head[a]},head[a]=edg++; } void inser() { int l=strlen(s),nxt=0; for(int i=0;i<l;i++) { int id=s[i]-'a'; if(!tree[id][nxt]) tree[id][nxt]=++tot; add(nxt,tree[id][nxt]); add(tree[id][nxt],nxt); nxt=tree[id][nxt]; cnt[nxt]++; } int en=nxt; nxt=0; for(int i=0;i<l-1;i++) { int id=s[i]-'a'; if(cnt[tree[id][nxt]]==1)add(tree[id][nxt],en); nxt=tree[id][nxt]; } } void bfs() { memset(dp,0x3f,sizeof dp); dp[0]=0; vis[0]=1; queue<int>q; for(q.push(0);!q.empty();q.pop()) { int v=q.front(); for(int i=head[v];~i;i=p[i].nxt) { int u=p[i].to; if(!vis[u]) { vis[u]++; dp[u]=dp[v]+1; q.push(u); } } } } int ans() { int l=strlen(s),mi=l,nxt=0,u; for(int i=0;i<l;i++) { int id=s[i]-'a'; if(tree[id][nxt])mi=min(dp[tree[id][nxt]]+l-1-i,mi); else break; nxt=tree[id][nxt]; } return mi; } int main() { //freopen("i.txt","r",stdin); //freopen("o.txt","w",stdin); memset(tree,0,sizeof tree); memset(head,-1,sizeof head); int n,m; scanf("%d %d",&n,&m); for(int i=0;i<n;i++) { scanf("%s",s); inser(); } bfs(); for(int i=0;i<m;i++) { scanf("%s",s); printf("%d\n",ans()); } }
反思:
这道题对于每个状态的互相转移关系,可以看作类似最短路,需要一点点思维吧(是不是可以用map映射id来做这道题呢)。
C Card Hand Sorting
输入:
扑克牌的花色和点数。
输出:
相同花色放在一起且牌成递增或者递减,最小需要的操作数。
思路:
扑克牌最后形成的结果只有1<<4*4的全排列中,枚举之
#include<bits/stdc++.h> using namespace std; pair<int,int>s[200]; int spoke[5][20],cnt[5],vis[5],p[5],n,lis[60],ans=0x3f3f3f3f; map<char,int>m; map<pair<int,int>,int>mm; void init() { m['s']=0,m['h']=1,m['d']=2,m['c']=3; m['T']=10,m['J']=11,m['Q']=12,m['K']=13,m['A']=14; memset(spoke,0,sizeof spoke); memset(cnt,0,sizeof cnt); memset(vis,0,sizeof vis); for(int i=2; i<=9; i++)m['0'+i]=i; } bool cmp(int a,int b) { return a>b; } void dfs(int inde) { if(inde==4) { for(int i=0;i<16;i++) { for(int j=0;j<4;j++)sort(spoke[j],spoke[j]+cnt[j]); for(int j=0;j<4;j++)if((1<<j)&i)sort(spoke[j],spoke[j]+cnt[j],cmp); mm.clear(); int k=0,ll=0,haha=0; for(int j=0;j<4;j++) { int tem=p[j]; for(int l=0;l<cnt[tem];l++) mm[{spoke[tem][l],tem}]=k++; } memset(lis,0x3f,sizeof lis); for(int j=0;j<n;j++) { ll=lower_bound(lis,lis+haha,mm[s[j]])-lis; haha=max(ll+1,haha); lis[ll]=mm[s[j]]; } ans=min(n-haha,ans); } } for(int i=0;i<4;i++) { if(!vis[i]) { vis[i]++; p[inde]=i; dfs(inde+1); vis[i]=0; } } } int main() { init(); scanf("%d ",&n); for(int i=0; i<n; i++) { char a,b; scanf("%c%c ",&a,&b); int inde=m[b]; s[i]={m[a],inde}; spoke[inde][cnt[inde]++]=m[a]; } dfs(0); cout<<ans<<endl; }
反思:
场上的时候一直在想怎么直接的贪心思路,对于答案情况很少的题可以直接枚举之。
D Daydreaming Stockbroker
思维,水题
E Exponial
数学,没补
F Fleecing the Raffle
推公式,水题
G Game Rank
模拟,水题
H Highest Tower
输入:
n个矩形的长宽
输出:
严格递减落成塔形,塔的最高高度
思路:
建树,出为底,入为高,出读最多的为1,(deg-1)是他最为高出现的次数,(deg-1)*h为他对于答案的贡献,如果图案是一个树,那么可以选择一个最大的点作为根节点,得到的答案最大,如果图案是一个图,那么形成的关系是唯一的直接计算即可。
#include<bits/stdc++.h> using namespace std; const int axn=550000+10; struct sc { int to,nxt; } p[2*axn]; int head[2*axn],edg=0,id=1,deg[axn],fm[axn],vis[axn],maxn; long long ans=0,degg; map<int,int>m; void add(int a,int b) { deg[a]++,deg[b]++; p[edg]= {b,head[a]},head[a]=edg++; p[edg]= {a,head[b]},head[b]=edg++; } void dfs(int u) { if(vis[u])return ; vis[u]=1; degg+=deg[u]-2; maxn=max(maxn,fm[u]); ans+=(long long)(deg[u]-1)*fm[u]; for(int i=head[u];~i;i=p[i].nxt) dfs(p[i].to); } int main() { memset(vis,0,sizeof vis); memset(head,-1,sizeof head); //freopen("i.txt","r",stdin); //freopen("o.txt","w",stdout); int n,x,y; scanf("%d",&n); for(int i=0; i<n; i++) { scanf("%d %d",&x,&y); if(!m[x])m[x]=id,fm[id++]=x; if(!m[y])m[y]=id,fm[id++]=y; add(m[x],m[y]); } for(int i=1;i<id;i++) { maxn=0,degg=0; dfs(i); if(degg<0)ans+=maxn; } cout<<ans<<endl; }
反思:
对于涉及矩形长宽的题,思考能否把边看成点建图解决。
I Interception
没补
J Jumbled Compass
思维,水题。
K Keeping the Dogs Apart
蓝书有板子,还没研究。