├── HDU 2896 病毒侵袭 【AC自动机】.md ├── HDU_1166 敌兵布阵 【线段树】.md ├── HDU_1251 统计难题 【字典树】.md ├── HDU_1305 Immediate Decodability 【字典树】.md ├── HDU_1465 不容易系列之一 【错排公式】.md ├── HDU_1599 find the mincost route【Dijkstra】.md ├── HDU_1599 find the mincost route【Floyd】.md ├── HDU_1671 POJ_3630 Phone List 【字典树】.md ├── HDU_2222 Keywords Search 【AC自动机】.md ├── HDU_2577 How to Type【dp】.md ├── HDU_2795 Billboard 【线段树】.md ├── HDU_2846 Repository 【字典树】.md ├── HDU_3460 Ancient Printer 【字典树】.md ├── HDU_6034 Balala Power! 【贪心】.md ├── HDU_6035 Colorful Tree 【DFS】.md ├── HDU_6038 Function 【DFS】.md ├── HDU_6050 Funny Function 【规律+逆元】.md ├── HDU_6052 To my boyfriend 【模拟】.md ├── HDU_6055 Regular polygon 【模拟】.md ├── HDU_6058 Kanade's sum 【链表】.md ├── HDU_6060 RXD and dividing 【DFS】.md ├── HDU_6063 RXD and math 【整数快速幂】.md ├── HDU_6069 Counting Divisors 【数论】.md ├── HDU_6070 Dirt Ratio 【线段树+二分】.md ├── HDU_6078 Wavel Sequence 【dp】.md ├── HDU_6090 Rikka with Graph 【贪心】.md ├── HDU_6092 Rikka with Subset 【dp】.md ├── HDU_6096 String 【字典树】.md ├── HDU_6097 Mindis 【几何】.md ├── HDU_6098 Inversion 【暴力】.md ├── HDU_6105 Gameia 【博弈】.md ├── HDU_6121 Build a tree 【DFS&&位运算技巧】.md ├── HDU_6127 Hard challenge 【思维】.md ├── HDU_6138 Fleet of the Eternal Throne 【AC自动机&&思维】【静态数组】.md ├── HDU_6140 Hybrid Crystals 【思维】.md ├── HDU_6152 Friend-Graph 【暴力】.md ├── HDU_6153 A Secret【扩展KMP】.md ├── HDU_6154 CaoHaha's staff 【规律打表】.md ├── HDU_6165 FFF at Valentine 【DFS】.md ├── HDU_6170 Numbers 【MAP&&思维】.md ├── HDU_6170 Two strings 【DP】.md ├── HDU_【2017 Multi-University Training Contest 1】.md ├── README.md └── 杭电多校联赛2017年总结.md /HDU 2896 病毒侵袭 【AC自动机】.md: -------------------------------------------------------------------------------- 1 | 2 | [题目链接](http://acm.hdu.edu.cn/showproblem.php?pid=2896) 3 | 4 | ### 题目意思 ### 5 | 6 | 给出n个病毒和n个网站,找出每一个网站中含有的病毒种类,并按病毒编号升序输出,最后统计含有病毒的网站个数。 7 | 8 | ### 解题思路 ### 9 | 10 | 这是一个AC自动机的模板题目。 11 | 这个题目给的字符串中的字符是除回车的任意字符,所以每个节点儿子是有128个的。 12 | 13 | 我们每个字典树的节点要维护的值有: 14 | 从root到该节点是不是一个完整的单词。 15 | 如果是完整的单词,它所代表单词的编号。 16 | fail指针。 17 | 由于我们的AC自动机要多个文本串去跑,所以我们再加一个vis标记以下那些文本串 18 | 已经上去匹配过了。 19 | 20 | ### 代码部分 ### 21 | 22 | ``` 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | 29 | using namespace std; 30 | 31 | const int allSon = 130; ///包含所有的字符 32 | const int maxn = 100005; 33 | int node[maxn][allSon]; ///字典树节点 34 | int fail[maxn]; ///fail指针 35 | int num[maxn]; ///num[i]代表以第i个节点为结尾的单词个数 36 | int vis[maxn]; ///标记 37 | int index[maxn]; ///用来存放模式串的编号 38 | char patten[maxn]; ///模式串 39 | char text[maxn]; ///文本串 40 | int ans[maxn]; 41 | int id; ///用来给节点编号 42 | int total; ///统计总共有多少个网站感染病毒 43 | ///将模式串插入到字典树,传入的模式串的编号 44 | void insertPatten(int identifier) 45 | { 46 | int p = 0; ///指向根节点 47 | int pos = 0; ///下标 48 | int len = strlen(patten); 49 | while(pos < len) 50 | { 51 | int ch = patten[pos]; 52 | if(node[p][ch]==-1) ///节点不存在 53 | { 54 | memset(node[id],-1,sizeof(node[id])); ///孩子节点都置空 55 | num[id] = 0; 56 | fail[id] = -1; 57 | vis[id] = -1; 58 | node[p][ch] = id++; 59 | } 60 | p = node[p][ch]; 61 | pos++; 62 | } 63 | num[p]++; 64 | index[p] = identifier; ///存储编号 65 | } 66 | ///找fail指针,构造AC自动机 67 | void build_AC_automaton() 68 | { 69 | queuequ; 70 | int p = 0; ///最开始指向根节点 71 | qu.push(p); 72 | while(!qu.empty()) 73 | { 74 | p = qu.front(); 75 | qu.pop(); 76 | for(int i = 0; i < allSon; i++) 77 | { 78 | if(node[p][i]!=-1) ///第i个孩子存在 79 | { 80 | if(p == 0) ///如果p是根,根的孩子fail指向根 81 | { 82 | fail[node[p][i]] = 0; 83 | } 84 | else 85 | { 86 | int temp = fail[p]; 87 | while(temp != -1) 88 | { 89 | if(node[temp][i] != -1) 90 | { 91 | fail[node[p][i]] = node[temp][i]; 92 | break; 93 | } 94 | temp = fail[temp]; 95 | } 96 | if(temp == -1) 97 | { 98 | fail[node[p][i]] = 0; 99 | } 100 | } 101 | qu.push(node[p][i]); 102 | } 103 | } 104 | } 105 | } 106 | ///在AC自动机中进行查询,传入参数是第i个网站的编号。 107 | void find_in_AC_automaton(int identify) 108 | { 109 | int p = 0; 110 | int pos = 0; 111 | int t = 0; 112 | while(text[pos]!='\0') 113 | { 114 | int ch = text[pos]; 115 | while(node[p][ch]==-1 && p!=0) 116 | p = fail[p]; 117 | p = node[p][ch]; 118 | if(p == -1) p = 0; 119 | int temp = p; 120 | /**由于别的字符串还要过来匹配,所以节点的num 121 | 值不能进行改变,我们可以用vis数组来实现标记,避免 122 | 重复统计**/ 123 | while(temp!=0 && vis[temp]!=identify) 124 | { 125 | vis[temp] = identify; 126 | if(num[temp]) ///代表网站有一个病毒 127 | { 128 | ans[t++] = index[temp]; 129 | } 130 | temp = fail[temp]; 131 | } 132 | ///由于一个网站病毒最多三个,所以找够三个就出来,不要浪费时间做无用功。 133 | if(t >= 3) 134 | break; 135 | pos++; 136 | } 137 | if(t != 0) 138 | { 139 | total++; 140 | printf("web %d:",identify); 141 | sort(ans,ans+t); 142 | for(int i = 0; i < t; i++) 143 | printf(" %d",ans[i]); 144 | printf("\n"); 145 | } 146 | } 147 | void init() 148 | { 149 | memset(node[0],-1,sizeof(node[0])); 150 | num[0] = 0; 151 | fail[0] = -1; 152 | index[0] = -1; 153 | vis[0] = -1; 154 | id = 1; 155 | total = 0; 156 | } 157 | int main() 158 | { 159 | int N,M; 160 | while(~scanf("%d",&N)) 161 | { 162 | init(); ///初始化根节点 163 | for(int i = 1; i <= N; i++) 164 | { 165 | scanf("%s",patten); 166 | insertPatten(i); 167 | } 168 | build_AC_automaton(); 169 | scanf("%d",&M); 170 | for(int i = 1; i <= M; i++) 171 | { 172 | scanf("%s",text); 173 | find_in_AC_automaton(i); 174 | } 175 | printf("total: %d\n",total); 176 | } 177 | return 0; 178 | } 179 | 180 | ``` -------------------------------------------------------------------------------- /HDU_1166 敌兵布阵 【线段树】.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | >[原题链接](http://acm.hdu.edu.cn/showproblem.php?pid=1166) 5 | 6 | ### 题目意思: ### 7 | 给定一个序列,有三种操作,最后输出给定范围的值之和 8 | ### 解题思路: ### 9 | 数据大,普通方法肯定超时,用线段树来计算区间的加法。 10 | ### 代码部分: ### 11 | 12 | ```cpp 13 | #include 14 | #include 15 | #include 16 | #define lchild left,mid,root<<1 //宏定义代替结构体 17 | #define rchild mid+1,right,root<<1|1 18 | using namespace std; 19 | 20 | const int maxn=50005; 21 | int Sum[maxn<<2]; 22 | int n; 23 | //更新线段树节点 24 | void push_up(int root) 25 | { 26 | Sum[root] = Sum[root<<1]+Sum[root<<1|1]; 27 | return ; 28 | } 29 | //构建线段树 30 | void build(int left,int right ,int root) 31 | { 32 | if(left == right) 33 | { 34 | int num; 35 | scanf("%d",&num); 36 | Sum[root] = num; 37 | return ; 38 | } 39 | int mid = (left+right)>>1; 40 | build(lchild); 41 | build(rchild); 42 | push_up(root); 43 | } 44 | 45 | //更改操作 46 | void update(int aim,int num,int left,int right,int root) 47 | { 48 | if(right == left) 49 | { 50 | Sum[root] += num; 51 | return ; 52 | } 53 | int mid = (left+right)>>1; 54 | if(mid>=aim) 55 | update(aim,num,lchild); 56 | else 57 | update(aim,num,rchild); 58 | push_up(root); 59 | } 60 | //查询操作 61 | int query(int L,int R,int left,int right,int root) 62 | { 63 | if(L <= left&&right <= R) 64 | { 65 | return Sum[root]; 66 | } 67 | int mid = (left+right)>>1; 68 | int ans = 0; 69 | //ans+=(L,R,lchild); 70 | //ans+=(L,R,rchild); 71 | if(R <= mid) 72 | { 73 | ans += query(L,R,lchild); 74 | } 75 | else if(L > mid) 76 | { 77 | ans += query(L,R,rchild); 78 | } 79 | else 80 | { 81 | ans += query(L,R,lchild); 82 | ans += query(L,R,rchild); 83 | } 84 | return ans; 85 | } 86 | int main () 87 | { 88 | int t,temp=1; 89 | scanf("%d",&t); 90 | while(t--) 91 | { 92 | scanf("%d",&n); 93 | build(1,n,1); 94 | string s; 95 | cout<<"Case "<>s) 97 | { 98 | int n1,n2; 99 | if(s[0] == 'E') 100 | break; 101 | if(s[0] == 'Q') 102 | { 103 | scanf("%d %d",&n1,&n2); 104 | int ans = query(n1,n2,1,n,1); 105 | cout< 3 | 4 | > [题目链接](http://acm.hdu.edu.cn/showproblem.php?pid=1251) 5 | 6 | ### 题目描述 ### 7 | 就是在给定的单词中求前缀的个数 8 | ### 解题思路 ### 9 | 字典树问题,统计前缀的个数。亲试用map也可以过。 10 | 不知道为什么,做字典树额题的时候,用g++ 一般都会超内存,改成c++就可以过。感觉这道题很有邪性,静态的动态数组,不是时间超限就是答案错误。改成静态的就过了。不过也是通过这道题尝试了动态的字典树构建,也是种收获吧。再有就是读回车结束,也是很少遇到,上来就是个措手不及,好在最后悠然解决。 11 | ### 代码部分 ### 12 | #### 动态字典树 #### 13 | ```cpp 14 | #include 15 | #include 16 | #include 17 | using namespace std; 18 | const int en = 26; 19 | const int maxn = 15; 20 | struct Trie 21 | { 22 | int cnt; 23 | Trie *nex[en]; 24 | }; 25 | int top; 26 | inline int idx(char c) 27 | { 28 | return c - 'a'; 29 | } 30 | inline Trie * CreatTrie() 31 | { 32 | Trie *p = (Trie *)(malloc)(sizeof(Trie)); 33 | p -> cnt = 0; 34 | for(int i = 0; i< en; ++ i) p -> nex[i] = NULL; 35 | return p; 36 | } 37 | inline Trie * Init() 38 | { 39 | top = 0; 40 | return CreatTrie(); 41 | } 42 | void Insert(Trie *root, string s) 43 | { 44 | Trie *p = root; 45 | for(int i = 0; i < s.size(); ++ i) 46 | { 47 | int temp = idx(s[i]); 48 | if(p -> nex[temp] == NULL) p -> nex[temp] = CreatTrie(); 49 | p = p -> nex[temp]; 50 | p -> cnt ++; 51 | } 52 | //p -> cnt --; 53 | } 54 | int Search(Trie *root, string s) 55 | { 56 | Trie *p = root; 57 | for(int i = 0; i < s.size(); ++ i) 58 | { 59 | int temp = idx(s[i]); 60 | if(p -> nex[temp] == NULL) return 0; 61 | p = p -> nex[temp]; 62 | } 63 | return p -> cnt; 64 | } 65 | int main() 66 | { 67 | ios::sync_with_stdio(false); 68 | string s; 69 | Trie *root = Init(); 70 | while(getline(cin, s, '\n') && s.size() != 0) Insert(root, s); 71 | while(cin >> s) cout << Search(root, s) << endl; 72 | return 0; 73 | } 74 | ``` 75 | 76 | ---- 77 | 78 | #### map #### 79 | ```cpp 80 | #include 81 | #include 82 | #include 83 | #include 84 | using namespace std; 85 | int main() 86 | { 87 | string c; 88 | int i; 89 | map m; 90 | while(getline(cin, c, '\n') && c.size() != 0) 91 | { 92 | for(i = c.size(); i > 0; -- i) 93 | m[c.substr(0,i)] ++; 94 | } 95 | string s; 96 | while(cin >> s) 97 | { 98 | cout << m[s] << endl; 99 | } 100 | return 0; 101 | } 102 | ``` 103 | 104 | ---- 105 | 106 | #### 静态字典树 #### 107 | 代码不过不了,不知道出了什么状况,如果有巨巨知道求指正。 108 | ```cpp 109 | #include 110 | using namespace std; 111 | const int en = 26; 112 | const int maxn = 1e3; 113 | struct Trie 114 | { 115 | int cnt; 116 | Trie *nex[en]; 117 | } T[maxn]; 118 | int top; 119 | inline int idx(char c) 120 | { 121 | return c - 'a'; 122 | } 123 | inline Trie * CreatTrie() 124 | { 125 | Trie *p = &T[top ++]; 126 | p -> cnt = 0; 127 | for(int i = 0; i< en; ++ i) p -> nex[i] = NULL; 128 | return p; 129 | } 130 | inline Trie * Init() 131 | { 132 | top = 0; 133 | return CreatTrie(); 134 | } 135 | void Insert(Trie *root, string s) 136 | { 137 | Trie *p = root; 138 | for(int i = 0; i < s.size(); ++ i) 139 | { 140 | int temp = idx(s[i]); 141 | if(p -> nex[temp] == NULL) p -> nex[temp] = CreatTrie(); 142 | p = p -> nex[temp]; 143 | p -> cnt ++; 144 | } 145 | //p -> cnt --; 146 | } 147 | int Search(Trie *root, string s) 148 | { 149 | Trie *p = root; 150 | for(int i = 0; i < s.size(); ++ i) 151 | { 152 | int temp = idx(s[i]); 153 | if(p -> nex[temp] == NULL) return 0; 154 | p = p -> nex[temp]; 155 | } 156 | return p -> cnt; 157 | } 158 | int main() 159 | { 160 | ios::sync_with_stdio(false); 161 | string s; 162 | Trie *root = Init(); 163 | while(getline(cin, s, '\n') && s.size() != 0) Insert(root, s); 164 | while(cin >> s) cout << Search(root, s) << endl; 165 | return 0; 166 | } 167 | ``` 168 | 169 | 170 | 171 | **另外学习到了其他读回车结束的代码** 172 | ```cpp 173 | while(1) 174 | { 175 | gets(s); 176 | if(strcmp(s,"")==0) 177 | break; 178 | } 179 | ``` 180 | -------------------------------------------------------------------------------- /HDU_1305 Immediate Decodability 【字典树】.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | > [题目链接](http://acm.hdu.edu.cn/showproblem.php?pid=1305) 4 | 5 | ### 题目描述 ### 6 | 不断输入字符串直到字符为"9"结束,如果在"9"之前,一个字符的代码不是另一个字符的代码的前缀,则这种情况为立即可解就输出"is immediately decodable"否则输出"is not immediately decodable" 7 | ### 解题思路 ### 8 | 字典树求前缀是否出现过,每插入一个字符就对字符进行查找。 9 | 插入时给字符最后的节点加1表示该节点存在。查找的过程:如果在向下扩展的过程中发现节点的cnt有值,就说明该字符已经存在,即为当前查找的字符的前缀,就让标记为假然后按格式输出就可以了。 10 | ### 代码部分 ### 11 | ```cpp 12 | #include 13 | using namespace std; 14 | const int en = 10; 15 | const int maxn = 1e6; 16 | bool flag; 17 | struct Trie 18 | { 19 | int cnt; 20 | Trie *nex[en]; 21 | } T[maxn]; 22 | int top; 23 | inline int idx(char c) 24 | { 25 | return c - '0'; 26 | } 27 | inline Trie * CreatTrie() 28 | { 29 | Trie *p = &T[top ++]; 30 | p -> cnt = 0; 31 | for(int i = 0; i< en; ++ i) p -> nex[i] = NULL; 32 | return p; 33 | } 34 | inline Trie * Init() 35 | { 36 | top = 0; 37 | return CreatTrie(); 38 | } 39 | void Insert(Trie *root, string s) 40 | { 41 | Trie *p = root; 42 | for(int i = 0; i < s.size(); ++ i) 43 | { 44 | int temp = idx(s[i]); 45 | if(p -> nex[temp] == NULL) p -> nex[temp] = CreatTrie(); 46 | p = p -> nex[temp]; 47 | } 48 | p -> cnt ++; 49 | } 50 | int Search(Trie *root, string s) 51 | { 52 | Trie *p = root; 53 | for(int i = 0; i < s.size(); ++ i) 54 | { 55 | int temp = idx(s[i]); 56 | if(p -> nex[temp] -> cnt && i != s.size() - 1) flag = false; 57 | p = p -> nex[temp]; 58 | } 59 | } 60 | int main() 61 | { 62 | ios::sync_with_stdio(false); 63 | int cas = 0; 64 | Trie *root = Init(); 65 | flag = true; 66 | string s; 67 | while(cin >> s) 68 | { 69 | if(s != "9") 70 | { 71 | Insert(root, s); 72 | Search(root, s); 73 | } 74 | else 75 | { 76 | if(flag) 77 | cout << "Set " << ++ cas << " is immediately decodable" << endl; 78 | else 79 | cout << "Set " << ++ cas << " is not immediately decodable" << endl; 80 | Trie *root = Init(); 81 | flag = true; 82 | } 83 | } 84 | return 0; 85 | } 86 | 87 | ``` 88 | -------------------------------------------------------------------------------- /HDU_1465 不容易系列之一 【错排公式】.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | >[题目链接](http://acm.hdu.edu.cn/showproblem.php?pid=1465) 4 | 5 | ### 题目意思: ### 6 | N个信封,全部装错位置的种类有多少种。 7 | ### 解题思路: ### 8 | 这是一道用错排公式可以解决的题目。 9 | a[1]=0; 10 | a[2]=1; 11 | a[n]=(n-1)*(a[n-1]+a[n-2]); 12 | 即可。 13 | ### 错排公式讲解: ### 14 | n封信放入n个信封,要求全部放错,共有多少种放法,记n个元素的错排总数为f(n)。 15 | 16 | 现在有n封信和n个信封,如果所有的信都装错了信封,求共有多少种错误方法。 17 | 18 | 当n=1和2时,易知道,假设F(n-1)和F(n-2)已经知道,重点分析下边的情况: 19 | 20 | 1. 当有n封信的时候,前边n-1封信或者n-2封信错装。 21 | 2. 前者,对于每一种错装,可以从前n-1封信中任选一封与第n封交换,那么就全部错装了。这种情况共有F(n-1)*(n-1)种方法。 22 | 3. 后者,只能将没装错的那封信与第n封信交换,没装错的那封信可以是前n-1封信中的任意一封,这种情况共有F(n-2)*(n-1)种F方法。 23 | 24 | ### 代码部分: ### 25 | 26 | ```cpp 27 | #include 28 | #include 29 | #include 30 | #define LL long long int 31 | using namespace std; 32 | 33 | int main() 34 | { 35 | LL a[21]; 36 | a[1] = 0; 37 | a[2] = 1; 38 | for(int i = 3; i <= 20; i++) 39 | a[i] = (i - 1)*(a[i - 1] + a[i - 2]); 40 | int n; 41 | while(cin >> n) 42 | { 43 | cout << a[n] << endl; 44 | } 45 | return 0; 46 | } 47 | 48 | ``` 49 | -------------------------------------------------------------------------------- /HDU_1599 find the mincost route【Dijkstra】.md: -------------------------------------------------------------------------------- 1 | ### 另种解法 2 | 如果i-j有边,先保存边的权值,然后删除这条边,再求i-j的最短路径,然后加上i-j这条边的值,在恢复i-j的边,对所有的边进行这样的操作,然后找出得到结果中的最小值。 3 | ### 代码部分 4 | ```cpp 5 | #include 6 | #include 7 | #include 8 | #define inf 0x3f3f3f3f 9 | using namespace std; 10 | 11 | const int maxn = 110; 12 | int n,m; 13 | int Map[maxn][maxn]; 14 | int dis[maxn]; 15 | int vis[maxn]; 16 | void init_map() 17 | { 18 | for(int i = 1; i <= n; i++) 19 | for(int j = 1; j <= n; j++) 20 | { 21 | if(i == j) Map[i][j]= 0; 22 | else Map[i][j] = inf; 23 | } 24 | } 25 | void dijkstra(int start) 26 | { 27 | for(int i = 1; i <= n; i++) 28 | { 29 | dis[i] = Map[start][i]; 30 | vis[i] = 0; 31 | } 32 | vis[start] = 1; 33 | int mindis,u; 34 | for(int i = 1; i <= n; i++) 35 | { 36 | mindis = inf; 37 | u = 0; 38 | /**每次让u = 0,不会与其他定点编号冲突,而且下个 39 | for循环执行完后,u还等于0,则程序就可以结束的, 40 | 在杭电写题得加上,不然的会显示RE,遇到过两次这样 41 | 的问题**/ 42 | for(int j = 1; j <= n; j++) 43 | { 44 | if(vis[j]==0 && dis[j]第二种方法参见队友的代码详情点链接:[温姑娘的博客]( http://blog.csdn.net/wyxeainn/article/details/75807616) -------------------------------------------------------------------------------- /HDU_1599 find the mincost route【Floyd】.md: -------------------------------------------------------------------------------- 1 | HDU_1599 find the mincost route【Floyd】 2 | 3 | >[原题链接](http://acm.hdu.edu.cn/showproblem.php?pid=1599) 4 | 5 | ### 题目意思: ### 6 | 给出N个景区,判断最少经过三个景区形成环的最少花费。 7 | ### 解题思路: ### 8 | 最初的想法,构成图以后,把其中一条路经给截断,然后算这条路径两点的最短路径,最后把所有这样的路径算完得出最小的答案,如果为无穷大,则输出It's impossible.否则输出最小的花费。但是就是过不了,Dijkstra 过不了,又是WA,又是RE。然后想用Floyd,但是使用方法不当,导致TLE。 9 | 好了,无限错以后,开始回归正解的思想。 10 | Floyd本身只是比较暴力的dp求各点之前的最短路径。但是,在Floyd更新的过程中也是有顺序的。可以借助这一点来解决这个题目。 11 | 首先先说一下如何来确定环的花费最小。开始先将ans赋值为无穷大。然后我们可以先选取两个点,两点之间的最短路径为`dis[i][j]`,再找一个大于i,j的一个点k,做松弛操作;进行下面的操作`ans=min(ans,dis[i][j] + mp[i][k] + mp[k][j]);`来不断更新环的最小花费。 12 | 当然Floyd的操作在松弛操作之后,可以保证i到j之间的最短距离不经过k。这样也可以保证经过的最小花费的路径为一个环。 13 | 如果还是不太了解,可以参考一下代码部分。 14 | ### 代码部分: ### 15 | ```cpp 16 | #include 17 | #define INF 1000000 18 | #define maxn 110 19 | using namespace std; 20 | int n,m; 21 | int mp[maxn][maxn];//邻接矩阵,用来存放路径 22 | int dis[maxn][maxn];//用来存储最两点之间的最短路径 23 | void initmp()//对mp的初始化 24 | { 25 | for(int i = 1; i <= n; i++) 26 | for(int j = 1; j <= n; j++) 27 | { 28 | if(i == j) 29 | mp[i][j] = mp[j][i] = 0; 30 | else 31 | mp[i][j] = mp[j][i] = INF; 32 | } 33 | } 34 | void floyd() //floyd函数 35 | { 36 | for(int i = 1; i <= n; i++) 37 | { 38 | for(int j = 1; j<= n; j++) 39 | { 40 | dis[i][j]=mp[i][j]; 41 | } 42 | } 43 | int ans = INF; 44 | for(int k = 1; k <= n; k++) 45 | { 46 | for(int i = 1; i < k; i++) 47 | { 48 | for(int j = i + 1; j < k; j++) 49 | { 50 | ans=min(ans,dis[i][j] + mp[i][k] + mp[k][j]);//在加入k更新最短路径之前进行求环最小花费的松弛操作 51 | } 52 | } 53 | for(int i = 1; i <= n; i++) //进行普通的Floyd 54 | { 55 | for(int j = 1; j <= n; j++) 56 | { 57 | if(dis[i][j] > dis[i][k] + dis[k][j]) 58 | dis[i][j] = dis[i][k] + dis[k][j]; 59 | } 60 | } 61 | } 62 | if(ans == INF) 63 | cout << "It's impossible." << endl; 64 | else 65 | cout << ans << endl; 66 | } 67 | int main() 68 | { 69 | 70 | int a,b,c; 71 | while(cin>>n>>m) 72 | { 73 | initmp(); 74 | for(int i = 1; i <= m; i++) 75 | { 76 | cin >> a >> b >> c; 77 | if(mp[a][b] > c)// 在给定的边可能会有重复,需要求最小的花费,所以需要更新重边 78 | { 79 | mp[a][b] = mp[b][a] = c; 80 | } 81 | } 82 | floyd(); 83 | } 84 | 85 | return 0; 86 | } 87 | 88 | ``` -------------------------------------------------------------------------------- /HDU_1671 POJ_3630 Phone List 【字典树】.md: -------------------------------------------------------------------------------- 1 | HDU_1671 POJ_3630 Phone List 【字典树】 2 | 3 | 4 | > [题目链接](http://poj.org/problem?id=3630) 5 | 6 | ### 题目描述 ### 7 | 给定一个电话号码列表,确定它是否一致,意思是没有数字是另一个的前缀。如果有输出NO,否则输出YES 8 | ### 解题思路 ### 9 | 字典树求前缀问题,但这里要注意,**可能一个电话号码是这个字符之前的前缀也可能是这个字符之后的前缀。**在插入字符的同时来判断该字符是否可以构成前缀即可。 10 | HDU这道题如果动态分配字典树不清除会内存超限。在POJ中动态分配内存时间还会超时。所以需要静态分配内存。从中知道了**动态分配内存会省内存但是时间限制会增加。静态分配内存会更耗内存。** 11 | ### 代码部分 ### 12 | #### 静态字典树 13 | ```cpp 14 | #include 15 | #include 16 | #include 17 | #include 18 | using namespace std; 19 | const int en = 10; 20 | const int maxn = 1e6; 21 | bool flag; 22 | struct Trie 23 | { 24 | int cnt; 25 | Trie *nex[en]; 26 | }T[maxn]; 27 | int top; 28 | inline int idx(char c) 29 | { 30 | return c - '0'; 31 | } 32 | inline Trie * CreatTrie() 33 | { 34 | Trie *p = &T[top ++]; 35 | p -> cnt = 0; 36 | for(int i = 0; i< en; ++ i) p -> nex[i] = NULL; 37 | return p; 38 | } 39 | inline Trie * Init() 40 | { 41 | top = 0; 42 | return CreatTrie(); 43 | } 44 | void Insert(Trie *root, string s) 45 | { 46 | Trie *p = root; 47 | for(int i = 0; i < s.size(); ++ i) 48 | { 49 | int temp = idx(s[i]); 50 | if(p -> nex[temp] == NULL) p -> nex[temp] = CreatTrie(); 51 | else if(p -> nex[temp] -> cnt || i == s.size() - 1)flag = false; 52 | p = p -> nex[temp]; 53 | } 54 | p -> cnt ++; 55 | } 56 | 57 | int main() 58 | { 59 | ios::sync_with_stdio(false); 60 | string s; 61 | int t, m; 62 | cin >> t; 63 | while(t --) 64 | { 65 | flag = true; 66 | Trie *root = Init(); 67 | cin >> m; 68 | while(m --) 69 | { 70 | cin >> s; 71 | Insert(root, s); 72 | } 73 | puts(flag ? "YES" : "NO"); 74 | } 75 | 76 | return 0; 77 | } 78 | 79 | ``` 80 | 81 | --- 82 | 83 | ### 动态字典树 84 | ```cpp 85 | #include 86 | #include 87 | #include 88 | #include 89 | using namespace std; 90 | const int en = 10; 91 | bool flag; 92 | struct Trie 93 | { 94 | int cnt; 95 | Trie *nex[en]; 96 | }; 97 | int top; 98 | inline int idx(char c) 99 | { 100 | return c - '0'; 101 | } 102 | inline Trie * CreatTrie() 103 | { 104 | Trie *p = (Trie *)(malloc)(sizeof(Trie)); 105 | p -> cnt = 0; 106 | for(int i = 0; i< en; ++ i) p -> nex[i] = NULL; 107 | return p; 108 | } 109 | inline Trie * Init() 110 | { 111 | top = 0; 112 | return CreatTrie(); 113 | } 114 | void Insert(Trie *root, string s) 115 | { 116 | Trie *p = root; 117 | for(int i = 0; i < s.size(); ++ i) 118 | { 119 | int temp = idx(s[i]); 120 | if(p -> nex[temp] == NULL) p -> nex[temp] = CreatTrie(); 121 | else if(p -> nex[temp] -> cnt || i == s.size() - 1)flag = false; 122 | p = p -> nex[temp]; 123 | } 124 | p -> cnt ++; 125 | } 126 | void Free(Trie *root) 127 | { 128 | for(int i = 0; i < en; ++ i) 129 | { 130 | if(root -> nex[i]) 131 | Free(root -> nex[i]); 132 | } 133 | free(root); 134 | } 135 | int main() 136 | { 137 | ios::sync_with_stdio(false); 138 | string s; 139 | int t, m; 140 | cin >> t; 141 | while(t --) 142 | { 143 | flag = true; 144 | Trie *root = Init(); 145 | cin >> m; 146 | while(m --) 147 | { 148 | cin >> s; 149 | Insert(root, s); 150 | } 151 | puts(flag ? "YES" : "NO"); 152 | Free(root); 153 | } 154 | 155 | return 0; 156 | } 157 | 158 | ``` -------------------------------------------------------------------------------- /HDU_2222 Keywords Search 【AC自动机】.md: -------------------------------------------------------------------------------- 1 | HDU_2222 Keywords Search 【AC自动机模板题】 2 | 3 | >[题目链接](http://acm.hdu.edu.cn/showproblem.php?pid=2222) 4 | 5 | ### 题目描述 ### 6 | 给定n个匹配串,和一个文本串,问有多少个匹配串在字符串中出现过。输出匹配串的个数。 7 | ### 解题思路 ### 8 | AC自动机模板题,AC自动机可以通过fail指针轻松的高效的进行字符串匹配,学习完AC自动机以后就可以很容易的解出这道题了。 9 | ### 代码部分 ### 10 | ```cpp 11 | #include 12 | using namespace std; 13 | typedef long long ll; 14 | const int en = 26; 15 | inline int idx(char c) 16 | { 17 | return c - 'a'; 18 | } 19 | struct Trie 20 | { 21 | Trie *nex[en]; 22 | Trie *fail; 23 | int cnt; 24 | }*root; 25 | 26 | inline Trie *Creat() 27 | { 28 | Trie *p = (Trie *)(malloc)(sizeof(Trie)); 29 | p -> cnt = 0; 30 | p -> fail = NULL; 31 | for(int i = 0; i < en; ++ i) p -> nex[i] = NULL; 32 | return p; 33 | } 34 | 35 | inline void Init() 36 | { 37 | root = Creat(); 38 | } 39 | 40 | void Insert(string s) 41 | { 42 | Trie *p = root; 43 | int len = s.size(); 44 | for(int i = 0; i < len; ++ i) 45 | { 46 | int t = idx(s[i]); 47 | if(p -> nex[t] == NULL) p -> nex[t] = Creat(); 48 | p = p -> nex[t]; 49 | } 50 | p -> cnt ++; 51 | } 52 | void BFail() 53 | { 54 | Trie *p = root; 55 | queue Q; 56 | Q.push(p); 57 | while(!Q.empty()) 58 | { 59 | p = Q.front(); 60 | Q.pop(); 61 | for(int i = 0; i < en; ++ i) 62 | { 63 | if(p -> nex[i]) 64 | { 65 | if(p == root) 66 | { 67 | p -> nex[i] -> fail = root; 68 | } 69 | else 70 | { 71 | Trie *q = p -> fail; 72 | while(q) 73 | { 74 | if(q -> nex[i]) 75 | { 76 | p -> nex[i] -> fail = q -> nex[i]; 77 | break; 78 | } 79 | q = q -> fail; 80 | } 81 | if(!q) p -> nex[i] -> fail = root; 82 | } 83 | Q.push(p -> nex[i]); 84 | } 85 | } 86 | } 87 | } 88 | int ACauto(string s) 89 | { 90 | int ans = 0; 91 | Trie *p = root; 92 | int len = s.size(); 93 | for(int i = 0; i < len; ++ i) 94 | { 95 | int t = idx(s[i]); 96 | while(!p -> nex[t] && p != root) p = p -> fail; 97 | p = p -> nex[t]; 98 | if(!p) p = root; 99 | Trie *q = p; 100 | while(q && q -> cnt != -1) 101 | { 102 | ans += q -> cnt; 103 | q -> cnt = -1; 104 | q = q -> fail; 105 | } 106 | } 107 | return ans; 108 | } 109 | inline void Free(Trie *p) 110 | { 111 | if(p) for(int i = 0; i < en; ++ i) Free(p -> nex[i]); 112 | free(p); 113 | } 114 | int main() 115 | { 116 | ios::sync_with_stdio(false); 117 | int t, n; 118 | string A, B; 119 | cin >> t; 120 | while(t --) 121 | { 122 | cin >> n; 123 | Init(); 124 | for(int i = 1; i <= n; ++ i) 125 | { 126 | cin >> A; 127 | Insert(A); 128 | } 129 | cin >> B; 130 | BFail(); 131 | cout << ACauto(B) << endl; 132 | free(root); 133 | } 134 | return 0; 135 | } 136 | 137 | ``` -------------------------------------------------------------------------------- /HDU_2577 How to Type【dp】.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | >[原题链接](http://acm.hdu.edu.cn/showproblem.php?pid=2577) 4 | 5 | ### 题目要求: ### 6 | 给出一个字符串,判断最少需要按多少下键盘才能打出这行带大写和小写的英文字符串。(起始的时候大写锁定键是关着的,要求结束的时候大写锁定键也是关的)。 7 | ### 解题思路: ### 8 | - **第一种方法模拟** 9 | 模拟的思路:对每个字符进行处理,分四种情况来考虑。 10 | //大写键未上锁遇到小写 11 | //大写键上锁遇到大写 12 | //大写键未上锁遇到大写来判断是否按大写锁定,还是按shift。 13 | //大写键上锁遇到小写来判断是否按大写锁定,还是按shift。 14 | 前两种情况直接让ans++即可。 15 | 后面的情况利用了贪心的思想,如果一个字母后面跟着两个不同形式的字母时,按大小写锁定键和shift是相同的按键数量,所以统一采用按大小写锁定键,如果只有一个不一样的,那就按shift即可。 16 | 17 | **代码如下:** 18 | 19 | ```cpp 20 | #include 21 | #include 22 | #include 23 | using namespace std; 24 | int main() 25 | { 26 | int n; 27 | cin >> n; 28 | while(n--) 29 | { 30 | string ch; 31 | cin>>ch; 32 | int len=ch.size(); 33 | int i,ans,flag; 34 | i = ans = flag = 0; 35 | while(i='a'&&ch[i]<='z') //大写键未上锁遇到小写 38 | ans++; 39 | if(flag==1&&ch[i]>='A'&&ch[i]<='Z') //大写键上锁遇到大写 40 | ans++; 41 | if(flag==0&&ch[i]>='A'&&ch[i]<='Z') //大写键未上锁遇到大写来判断是否按大写锁定,还是按shift。 42 | { 43 | if(i+1='A'&&ch[i+1]<='Z') 46 | { 47 | flag=1; 48 | ans+=2; 49 | } 50 | else 51 | { 52 | ans+=2; 53 | } 54 | } 55 | else 56 | ans+=2; 57 | 58 | } 59 | if(flag==1&&ch[i]>='a'&&ch[i]<='z')//大写键上锁遇到小写来判断是否按大写锁定,还是按shift。 60 | { 61 | if(i+1='a'&&ch[i+1]<='z') 64 | { 65 | flag=0; 66 | ans+=2; 67 | } 68 | else 69 | { 70 | ans+=2; 71 | } 72 | } 73 | else 74 | { 75 | ans+=2; 76 | flag=0; 77 | } 78 | } 79 | i++; 80 | } 81 | if(flag==1) 82 | ans+=1; 83 | printf("%d\n",ans); 84 | } 85 | return 0; 86 | } 87 | 88 | ``` 89 | - **第二种方法动态规划** 90 | 动态规划思路:定义`dp[0][i]`为小写状态下敲击完第i个字母时最小敲击次数,`dp[1][i]`为大写状态下敲击完第i个字母时最小敲击次数。很容易可以找出转移方程。 91 | 92 | **代码如下:** 93 | 94 | ```cpp 95 | #include 96 | #define maxn 110 97 | using namespace std; 98 | 99 | int dp[2][maxn]; 100 | int main() 101 | { 102 | int t; 103 | char s[maxn]; 104 | scanf("%d", &t); 105 | while(t--) 106 | { 107 | scanf(" %s", s + 1); 108 | int len = strlen(s + 1); 109 | dp[0][0] = 0, dp[1][0] = 1; 110 | for(int i = 1; s[i]; i++) 111 | { 112 | if(s[i] >= 'a' && s[i] <= 'z')//当前是小写字母 113 | { 114 | dp[0][i] = min(dp[0][i-1] + 1, dp[1][i-1] + 2);//小写状态直接按字母,大写状态按CapsLock+字母,取较小值 115 | dp[1][i] = min(dp[0][i-1] + 2, dp[1][i-1] + 2);//小写状态按字母+CapsLock,大写状态按shift+字母,取较小值 116 | } 117 | else//大写字母 118 | { 119 | dp[0][i] = min(dp[0][i-1] + 2, dp[1][i-1] + 2);//小写状态按shift+字母,大写状态按字母+CapsLock,取较小值 120 | dp[1][i] = min(dp[0][i-1] + 2, dp[1][i-1] + 1);//小写状态按CapsLock+字母,大写状态直接按字母,取较小值 121 | } 122 | } 123 | dp[1][len] += 1;//大写需要变成小写锁定 124 | printf("%d\n", min(dp[0][len], dp[1][len])); 125 | } 126 | return 0; 127 | } 128 | 129 | ``` 130 | -------------------------------------------------------------------------------- /HDU_2795 Billboard 【线段树】.md: -------------------------------------------------------------------------------- 1 | HDU_2795 Billboard 【线段树】 2 | 3 | 4 | >[原题链接](http://acm.hdu.edu.cn/showproblem.php?pid=2795) 5 | 6 | ### 题目意思: ### 7 | 给定一个公告板,让你贴公告,公告是尽量往左上角贴, 如果可以贴,输出行号,如果不能贴,输出-1. 8 | ### 解题思路: ### 9 | 以公告板的高度作为构建线段树的标准,另外用一个数组来存储线段树每个节点的宽度。贴公告的时候从左子树往右子树来贴,这样可以保证是从上到下来贴。具体线段树的代码看看下面的代码部分吧。 10 | ### 代码部分: ### 11 | 12 | ```cpp 13 | #include 14 | #include 15 | #define lchild left,mid,root<<1 16 | #define rchild mid+1,right,root<<1|1 17 | using namespace std; 18 | 19 | const int maxn = 1e8; 20 | int Max[maxn<<2]; 21 | int height,width,n; 22 | ///构建线段树 23 | void build(int left,int right,int root)//让所有子树的根的最大宽度都为width 24 | { 25 | Max[root] = width; 26 | if(left == right) 27 | return ; 28 | int mid = (left+right)>>1; 29 | build(lchild); 30 | build(rchild); 31 | } 32 | ///更新线段树 33 | void push_up(int root) 34 | { 35 | Max[root] = max(Max[root<<1],Max[root<<1|1]);//找出左右子树最大的宽度付给父节点 36 | } 37 | ///查询并插入 38 | int query(int w,int left,int right,int root) 39 | { 40 | if(left == right)//将该广告插入当前的节点并保存当前节点的最大宽度 41 | { 42 | Max[root] -= w; 43 | return left;//返回行号 44 | } 45 | int mid = (left+right)>>1; 46 | int ans = 0; 47 | if(w <= Max[root<<1])//如果左子树中有大于w的边,就将其插入左子树 48 | ans = query(w,lchild); 49 | else 50 | ans = query(w,rchild); 51 | push_up(root);//进行更新操作 52 | return ans; 53 | } 54 | 55 | int main() 56 | { 57 | while(~scanf("%d%d%d",&height,&width,&n)) //换做cin会超时 58 | { 59 | if(n 3 | 4 | > [题目链接](http://acm.hdu.edu.cn/showproblem.php?pid=2846) 5 | 6 | ### 题目描述 ### 7 | 给定n个字符串然后再给出m个查询,问每次查询的字符串s是那n个字符串子串的个数。注意如果查询的为ab,存在一个字符串abab,那么在这个abab的字符串中,只能计数1而不是2,也就是说一个字符串只能计数1次。 8 | ### 解题思路 ### 9 | Trie能很快的求出字典中以某个串为前缀的串的个数 但现在要查的是以某个串为子串的串的个数 可以发现 10 | 一个串的任何子串肯定是这个串某个后缀的前缀 如"ri"是“Trie" 的子串 是后缀 "rie" 的前缀 11 | 那么我们在向Trie中插入时可以把这个串的所有后缀都插入 插入时要注意来自同一个串的后缀的相同前缀只能统计一次 如 "abab" 这个串 "ab" 既是后缀 "abab" 的前缀 也是后缀 "ab" 的前缀 但是只能统计一次 这用一个id数组标记就行了 12 | 这样最后Trie中以查询串为前缀的串的个数就是原始串中以查询串为子串的串的的个数了 13 | ### 代码部分 ### 14 | ```cpp 15 | #include 16 | using namespace std; 17 | 18 | const int maxn = 5e5 + 10; 19 | int nex[maxn][26]; 20 | int cnt[maxn], id[maxn]; 21 | int top, root, ans; 22 | inline int idx(char c) 23 | { 24 | return c - 'a'; 25 | } 26 | inline void Init() 27 | { 28 | top = 0; 29 | memset(nex, 0, sizeof(nex)); 30 | memset(cnt, 0, sizeof(cnt)); 31 | memset(id, 0, sizeof(id)); 32 | root = top ++; 33 | } 34 | void Insert(string s, int x) 35 | { 36 | int len = s.size(); 37 | int now = root; 38 | for(int i = 0; i < len; ++ i) 39 | { 40 | int temp = idx(s[i]); 41 | if(!nex[now][temp]) nex[now][temp] = top ++; 42 | now = nex[now][temp]; 43 | if(id[now] != x) cnt[now] ++; 44 | id[now] = x; 45 | } 46 | } 47 | int Search(string s) 48 | { 49 | int now = root; 50 | int len = s.size(); 51 | for(int i = 0; i < len; ++ i) 52 | { 53 | int temp = idx(s[i]); 54 | if(!nex[now][temp]) return 0; 55 | now = nex[now][temp]; 56 | } 57 | return cnt[now]; 58 | } 59 | int main() 60 | { 61 | ios::sync_with_stdio(false); 62 | int n; string s; 63 | cin >> n; 64 | Init(); 65 | for(int i = 1; i <= n; ++ i) 66 | { 67 | cin >> s; 68 | for(int j = 0; j < s.size(); ++ j) 69 | { 70 | string c = s; 71 | c.erase(c.begin(), c.begin() + j); 72 | Insert(c, i); 73 | } 74 | } 75 | cin >> n; 76 | while(n --) 77 | { 78 | cin >> s; 79 | cout << Search(s) << endl; 80 | } 81 | return 0; 82 | } 83 | ``` -------------------------------------------------------------------------------- /HDU_3460 Ancient Printer 【字典树】.md: -------------------------------------------------------------------------------- 1 | HDU_3460 Ancient Printer 【字典树】 2 | 3 | > [题目链接](http://acm.hdu.edu.cn/showproblem.php?pid=3460) 4 | 5 | ### 题目描述 ### 6 | 现有一个打印机,有3个功能: 7 | 8 | - 输入'a'~'z' 9 | - 删除一个字母 10 | - 输出打印机中的字符串 11 | 12 | 问给定n个字符串,要将他们都输出,最少需要多少次操作。 13 | ### 解题思路 ### 14 | 首先,这道题的输出可以先不用考虑,因为有几个单词需要输出几次。再有就是输入和删除,你将其所有单词插入到字典树当中,树中有多少节点就进行多少次输入和删除。最后因为最长的没有删除,再减去最长的长度即可。 15 | ### 代码部分 ### 16 | 因为时间限制较少,所以优先考虑静态建字典树。 17 | ```cpp 18 | #include 19 | using namespace std; 20 | const int maxn = 5e5 + 10; 21 | int nex[maxn][26]; 22 | int cnt[maxn]; 23 | int top, root, ans, Max;///ans 节点的个数 24 | inline int idx(char c) {return c - 'a';} 25 | inline int Creat() 26 | { 27 | for(int i = 0; i< 26; ++ i) nex[top][i] = 0; 28 | cnt[top] = 0; 29 | return top ++; 30 | } 31 | inline void Init() 32 | { 33 | top = 0, ans = 0, Max = -1; 34 | memset(nex, 0, sizeof(nex)); 35 | memset(cnt, 0, sizeof(cnt)); 36 | root = Creat(); 37 | } 38 | void Insert(string s) 39 | { 40 | int len = s.size(); 41 | int now = root; 42 | for(int i = 0; i < len; ++ i) 43 | { 44 | int temp = idx(s[i]); 45 | if(!nex[now][temp]) nex[now][temp] = Creat(); 46 | now = nex[now][temp]; 47 | cnt[now] ++; 48 | } 49 | } 50 | void Search(int x) 51 | { 52 | for(int i = 0; i < 26; ++ i) if(nex[x][i]) {ans ++, Search(nex[x][i]);} 53 | } 54 | int main() 55 | { 56 | ios::sync_with_stdio(false); 57 | int n; 58 | string s; 59 | while(cin >> n) 60 | { 61 | Init(); 62 | for(int i = 1; i <= n; ++ i) 63 | { 64 | cin >> s; 65 | int len = s.size(); 66 | if(len > Max) Max = s.size(); 67 | Insert(s); 68 | } 69 | Search(0); 70 | cout << ans * 2 - Max + n << endl; 71 | } 72 | return 0; 73 | } 74 | 75 | ``` -------------------------------------------------------------------------------- /HDU_6034 Balala Power! 【贪心】.md: -------------------------------------------------------------------------------- 1 | ### 1002. Balala Power! ### 2 | 3 | >[题目链接](http://acm.hdu.edu.cn/showproblem.php?pid=6034) 4 | 5 | 6 | #### 题目意思: #### 7 | 巴拉巴拉变成猪!!! 讲意思之前先吐槽一番,做题简直就像是做阅读理解。 8 | 咳咳,回归正题: 9 | 输入几个只有小写英文字母字符串,a-z可以用0-25来任意代替,但要尽可能的取最大值。取值的过程因为数值过大,要不断的对1e9+7取余。 10 | 一行题意看原文看小半个小时TT。 11 | #### 解题思路 #### 12 | 每个字符对答案的贡献都可以看作一个 26 进制的数字,问题相当于要给这些贡献加一个 0 到 25 的权重使得答案最大。最大的数匹配 25,次大的数匹配 24,依次类推。排序后这样依次贪心即可,唯一注意的是不能出现前导 0。 13 | #### 难点坑点: #### 14 | 1. 给字母赋值 15 | 2. 前导零 16 | 17 | **给字母赋值:**将字母的权值位数用一个二维数组进行存储,然后将每一位都进行从小到大的排序,这样就可以给字母赋值了 18 | **前导零处理:**前面已经将所有的字母的比重按从小到大的顺序排好,所以在排除前导零的过程为了保证的到的值最大,所以要排除比重最小的前导可能为零的情况。具体实现就看代码部分吧。 19 | #### 代码部分: #### 20 | 21 | ```cpp 22 | #include 23 | using namespace std; 24 | typedef long long LL; 25 | 26 | const int N = 100020; //最大的字符串长度,这里注意需要开大一点,在计算权值的时候防止溢出 27 | const int Q = 1e9 + 7; //MOD 28 | int n, L;//L为输入的字符串的最大长度 29 | int num[26][N];//权值位个数,对其进行sort排序,判断应赋予的值 30 | int power[N], sum[N];//计算字符串的和所用到,power表权重,sum表字母的权重和 31 | bool ban[26]; //标记前导0 32 | 33 | char str[N]; 34 | int a[26]; //用于cmp排序 35 | 36 | bool cmp(int A, int B) 37 | { 38 | for (int i = L - 1 ; i >= 0 ; -- i) 39 | { 40 | if (num[A][i] != num[B][i]) 41 | { 42 | return num[A][i] < num[B][i]; 43 | } 44 | } 45 | return 0; 46 | } 47 | 48 | void work() 49 | { 50 | memset(num, 0, sizeof(num)); 51 | memset(ban, 0, sizeof(ban)); 52 | memset(sum, 0, sizeof(sum)); 53 | L = 0; 54 | ///排列字母的比重,过程有累加字母的权值位数,通过权值位数进行排序。 55 | ///在这个过程中,也对字符串的相加做了铺垫。 56 | ///注意过程中,需要不断得对mod取余 57 | for (int i = 0 ; i < n ; ++ i) 58 | { 59 | scanf("%s", str); 60 | int len = strlen(str); 61 | if (len > 1)//如果字符串的长度大于一,表示其前导不能为零 ban从0变成1 62 | { 63 | ban[str[0] - 'a'] = 1; 64 | } 65 | reverse(str, str + len); //字符串反转 66 | 67 | for (int j = 0 ; j < len ; ++ j) 68 | { 69 | ++ num[str[j] - 'a'][j]; 70 | sum[str[j] - 'a'] += power[j];///? sum存放权值,用于求和所用 71 | if (sum[str[j] - 'a'] >= Q) //还要不断对mod取余 72 | { 73 | sum[str[j] - 'a'] -= Q; 74 | } 75 | } 76 | L = max(L, len);//刷新其输入的字符串的最大长度 77 | } 78 | for (int i = 0 ; i < 26 ; ++ i) //对权值进位 79 | { 80 | for (int j = 0 ; j < L ; ++ j) 81 | { 82 | num[i][j + 1] += num[i][j] / 26; 83 | num[i][j] %= 26; 84 | } 85 | while (num[i][L])//将最高位向后拓展 86 | { 87 | num[i][L + 1] += num[i][L] / 26; 88 | num[i][L ++] %= 26; 89 | } 90 | a[i] = i; 91 | } 92 | sort(a, a + 26, cmp); 93 | ///前面的sort过程已经将所有的字母的比重按从小到大的顺序排好, 94 | ///所以在排除前导零的过程为了保证的到的值最大,所以要排除比重最小的前导可能为零的情况。 95 | ///排除前导0的过程 96 | ///从小到大可以保证求得的值最大 97 | int zero = -1; 98 | for (int i = 0 ; i < 26 ; ++ i) 99 | { 100 | if (!ban[a[i]])//找到未标记前导零的权重最小的一个字母赋值为0 101 | { 102 | zero = a[i]; 103 | break; 104 | } 105 | } 106 | ///计算字符串之和,二十六进制转十进制求和 107 | int res = 0, x = 25; 108 | for (int i = 25 ; i >= 0 ; -- i) 109 | { 110 | if (a[i] != zero) 111 | { 112 | res += (LL)(x --) * sum[a[i]] % Q; 113 | res %= Q; 114 | } 115 | } 116 | static int ca = 0; 117 | printf("Case #%d: %d\n", ++ ca, res); 118 | } 119 | 120 | int main() 121 | { 122 | //N 为最大位数 123 | power[0] = 1; 124 | ///每位上的权值(需要在计算的过程中不断对Q取余),字符串相加中会用到 125 | for (int i = 1 ; i < N ; ++ i) 126 | { 127 | power[i] = (LL)power[i - 1] * 26 % Q; 128 | } 129 | while (~scanf("%d", &n)) 130 | { 131 | work(); 132 | } 133 | return 0; 134 | } 135 | 136 | ``` 137 | -------------------------------------------------------------------------------- /HDU_6035 Colorful Tree 【DFS】.md: -------------------------------------------------------------------------------- 1 | ### 1003. Colorful Tree ### 2 | > [题目链接](http://acm.hdu.edu.cn/showproblem.php?pid=6035) 3 | 4 | #### 题目意思: #### 5 | 给出n个点的颜色,n-1条路径,任意两点之间路径的权值为路径中不同颜色的数目,求所有路径的权值之和 6 | #### 解题思路: #### 7 | 逆序思维,这道题就可以转换为,假设所有的路径都经过所有的颜色,然后将其减去i颜色没有经过的路径之和,就可以算出与题目所求相同的结果。 8 | #### 解题实现: #### 9 | 所有路径都经过所有颜色,这时候权值可以为颜色的数量乘以总的路径数即 num((n-1)n/2)。 10 | 这道题目的难点就是过来求颜色为i的时候,没有经过颜色i的路径之和为多少。 11 | 没有经过颜色i的分两种: 12 | 1. 在子树中,部分节点k个都没有经过i颜色,那么路径的条数就应该加上(k-1)k/2。该过程在dfs的函数中实现。 13 | 2. 再算子树的过程中,都是算的没有经过根节点的路径,这时候就可以借助一个sum数组来求出剩下的所有没有经过的i颜色的路径条数。 14 | 15 | 在dfs的过程中,如果找到子树中k个点都没有经过i颜色,就应该把这几个点组成的路径条数给算上,然后将其颜色模拟成和i颜色相同(sum[i] += k,模拟过程只是改变sum[i]数组的值,并未改变其本身的颜色)。 16 | 这样在将根节点包含进去的时候,就不会重复计算子节点中的颜色不同的路径了。经过dfs最后得到的sum[]就表示,整棵树中,模拟过的i颜色的点的个数有几个,用ct = n-sum[i]表示没有经过颜色i的点的个数有多少个,进而可以推出路径的条数。 17 | #### 代码实现: #### 18 | 19 | ```cpp 20 | #include 21 | using namespace std; 22 | const int N = 4e5+100; 23 | typedef long long ll; 24 | int vis[N];///c[i]表示第i个点的颜色,vis[i]表示的是i这个颜色有没有出现 25 | 26 | //DFS 中需要用到的 27 | vector e[N];///存储这个图呢 28 | ll c[N],sum[N],size[N];///size[i]表示的是以i为根的点的个数,c[x]可以表示为x的颜色。sum[i]模拟颜色改变后i颜色在整个树中的个数。 29 | ll ans;//表示没有经过i颜色的路径个数(i : 1~n) 30 | 31 | void dfs(int x, int y)//x代表当前节点y代表上一个节点 32 | { 33 | 34 | size[x] = 1;/// 指的是x为根的树的点的个数 35 | sum[c[x]] ++; 36 | ll pre = sum[c[x]]; 37 | for(int i = 0; i < e[x].size(); i++) 38 | { 39 | 40 | if(e[x][i] == y) 41 | continue; 42 | dfs(e[x][i], x); 43 | 44 | 45 | size[x] += size[e[x][i]];/// 以x为根的树的点的总个数,当前的这个点还要加上他的子数上的点 46 | ll count = size[e[x][i]] - (sum[c[x]] - pre);//count 表示子树中无x点颜色的个数,用来求子树中没有包含x的路径的个数。 47 | ans = ans + (1LL * count * (count - 1)) / 2;//算出子树中的无某种颜色的个数 48 | sum[c[x]] += count;//将算入路径中的颜色模拟成x的颜色。这里注意,如果子树中无x颜色的点的个数为1时,也是构不成路径,但是也要算在sum[x]里面,因为这样的节点在整棵树中也是无法构成路径的 49 | pre = sum[c[x]]; 50 | } 51 | } 52 | 53 | int main() 54 | { 55 | int n,cas=1; 56 | while(scanf("%d",&n)!=EOF) 57 | { 58 | int num=0; 59 | ans=0; 60 | memset(sum,0,sizeof(sum)); 61 | memset(vis,0,sizeof(vis)); 62 | for(int i=1; i<=n; i++) 63 | { 64 | e[i].clear(); 65 | scanf("%d",&c[i]); 66 | if(!vis[c[i]]) 67 | { 68 | vis[c[i]]=1;///标记这个颜色出现 69 | num++;///不同的颜色数 70 | } 71 | } 72 | for(int i=1; i[题目链接](http://acm.hdu.edu.cn/showproblem.php?pid=6038) 4 | 5 | #### 题目意思: #### 6 | 给两个序列a,b;问其满足关系式`f(i) = b[f(a[i])]`;有多少种。 7 | 8 | #### 解题思路: #### 9 | 若想满足关系式,则a与a[i]需要成环,b与b[i]需要成环,并且a与b需要同时成环时,又因为a环包着b环,则当b环长度为a环长度的约数的时候就可以满足关系式。 10 | 其中a环和b环是不会相交的。 11 | 若a的其中一个循环节长度为l,那么b循环节肯定为l的因数,把满足条件的b环将其长度与数量相乘再与a循环节个数相乘。答案累加就可以了。 12 | #### 难点: #### 13 | 1. 为什么a与a[i]是环,b与b[i]是环。 14 | 2. 怎么去求a环和b环 15 | 16 | - 由关系式可以看出当a有这样的关系的时候才会满足关系式。`i->a[i]->a[a[i]]->……i`;将关系式`f(i) = b[f(a[i])]`往下扩展一下`f(i) = b[f(a[i])] = b[b[f(a[i])]]`,由此可以看出b与b[i]有同样的关系。 17 | - 通过一个标记数组和递归进行求,从上面环的解释可以看出,a环与b环的求解过程是一样的。 18 | 19 | #### 代码部分: #### 20 | 21 | ```cpp 22 | #include 23 | using namespace std; 24 | #define ll long long 25 | #define M 1000000007 ///取模 26 | 27 | const int maxn = 100100; 28 | int n, m, cal[2][maxn];///存放长度为l时a环的个数和b环的个数 29 | int a[maxn], b[maxn], ans = 1;///存放a,b数组的值 30 | bool vis[maxn];///标记数组,判断是否在其他的环中 31 | 32 | ///递归求环长为l时的个数 33 | void dfs(int t, int l, int *a, int k) 34 | { 35 | if(vis[t]) 36 | { 37 | cal[k][l]++; 38 | return; 39 | } 40 | vis[t] = 1; 41 | dfs(a[t], l + 1, a, k); 42 | } 43 | 44 | int main() 45 | { 46 | int ca = 0; 47 | while(~scanf("%d%d", &n, &m)) 48 | { 49 | for(int i = 0; i < n; i++) 50 | scanf("%d", a + i); 51 | for(int i = 0; i < m; i++) 52 | scanf("%d", b + i); 53 | memset(cal, 0, sizeof(cal)); 54 | ///递归求存在的b环的个数 55 | memset(vis, 0, sizeof(vis)); 56 | for(int i = 0; i < m; i++) 57 | if (!vis[i]) 58 | dfs(i, 0, b, 0); 59 | ///递归求存在的a环的个数 60 | memset(vis, 0, sizeof(vis)); 61 | for(int i = 0; i < n; i++) 62 | if (!vis[i]) 63 | dfs(i, 0, a, 1); 64 | ///计算a,b互成环数量 65 | ///已知b环为a环的约数 66 | ///将所有的a环都乘以环长度的约束的所有的b环得出答案 67 | ans = 1; 68 | for(int i = 1; i <= n; i++) 69 | if(cal[1][i]) //如果a环在长度为i的时候存在环 70 | { 71 | int lim = (int)sqrt(i + 0.5), ta = 0;//ta 用来累加当a环存在的时候b环的总数 72 | for(int j = 1; j <= lim; j++) 73 | if(i % j == 0) //如果是a环长度的约数 74 | { 75 | (ta += (ll)cal[0][j] % M * j % M) %= M; 76 | if (j * j != i) 77 | (ta += (ll)cal[0][i / j] % M * (i / j) % M) %= M; 78 | } 79 | for(int j = 1; j <= cal[1][i]; j++) 80 | ans = (ll)ans * ta % M; 81 | } 82 | printf("Case #%d: %d\n", ++ca, ans); 83 | } 84 | return 0; 85 | } 86 | 87 | 88 | 89 | ``` 90 | 91 | -------------------------------------------------------------------------------- /HDU_6050 Funny Function 【规律+逆元】.md: -------------------------------------------------------------------------------- 1 | HDU_6050 Funny Function 【规律+逆元】 2 | 3 | 4 | > [题目链接](http://acm.hdu.edu.cn/showproblem.php?pid=6050) 5 | 6 | ### 题目意思: ### 7 | 根据题中所给的规律,求出给定的m,n的时候,F(m,1)的值为多少。因为在求解过程中,得到的结果会比较大,所以在求解的过程中需要不断得对`1e9+7`取余。 8 | ### 解题思路: ### 9 | 为了讲解过程方便,现将其打表: 10 | ``` 11 | n=2 12 | 1 1 3 5 11 21 43 85 13 | 2 4 8 16 32 64 128 256 14 | 6 12 24 48 96 192 384 768 15 | 18 36 72 144 288 576 1152 2304 16 | n=3 17 | 1 1 3 5 11 21 43 85 18 | 5 9 19 37 75 149 299 597 19 | 33 65 131 261 523 1045 2091 4181 20 | 229 457 915 1829 3659 7317 14635 29269 21 | n=4 22 | 1 1 3 5 11 21 43 85 23 | 10 20 40 80 160 320 640 1280 24 | 150 300 600 1200 2400 4800 9600 19200 25 | 2250 4500 9000 18000 36000 72000 144000 288000 26 | n=5 27 | 1 1 3 5 11 21 43 85 28 | 21 41 83 165 331 661 1323 2645 29 | 641 1281 2563 5125 10251 20501 41003 82005 30 | 19861 39721 79443 158885 317771 635541 1271083 2542165 31 | 615681 1231361 2462723 4925445 9850891 19701781 39403563 78807125 32 | ``` 33 | 34 | 35 | 要求的是F(m,1),则肯定与上面的结果有关系的,数据量过大,肯定是没有办法循环得到结果,由此看出,这道题必是规律题。 36 | 37 | --- 38 | 当n为奇数和偶数的时候,求F(m,1)的规律是不一样的。 39 | 40 | - **偶数:** 打表可以看出当n为偶数的时候,从F(2,1)开始,第一列的下一个是上一个的k倍,多打机组数据可看出,k为`3,15,63,255……`k的规律就是`2^n-1`。由此可得出,当n为偶数的时候,`F(m,1) = F(2,1)*(2^n-1)^(m-2)`。 41 | - **奇数:** 奇数求解的过程就相对来说比较复杂一点了。当n为奇数的时候,可以发现,F(m-1,1)*(2^n-1)与F(m,1)之间为一个常数k。我们就可以进一步的通过这样的关系求出F(m,1)的值 42 | 43 | **奇数求解过程:**通过打表可以看出,当n等于`3,5,7,9……`的时候,k的值为`2,10,42,170……` 44 | 可以看出之间的规律: 45 | ``` 46 | 2 = 2^1 47 | 10 = 2^1+2^3 48 | 42 = 2^1+2^3+2^5 49 | 170 = 2^1+2^3+2^5+2^7 50 | …… 51 | //可以看出是等比为2^2的前k项和 52 | ``` 53 | 54 | --- 55 | 56 | 结合上面可以得出:`F(m-1,1)*(2^n-1)-F(m,1) = Sk` 57 | `F(m,1) = F(m-1,1)*(2^n-1)-Sk;` 58 | 又可以根据这个公式推出 59 | 60 | ![](http://115.159.67.147/wp-content/uploads/2017/07/FF.png) 61 | 62 | --- 63 | 64 | 算到这里就仅仅剩下F(2,1)的求解了 65 | 先打表看看n:(1~i)第一行与F(2,1)的关系 66 | ``` 67 | S[i]: 1 1 3 5 11 21 43 85 171 341 683 1365 2731 5461 10923 21845 43691 87381 174763 349525 68 | F[i]: 1 2 5 10 21 42 85 170 341 682 1365 2730 5461 10922 21845 43690 87381 174762 349525 69 | ``` 70 | F[i]表示当n为i时F(2,1)的值。 71 | 当i为奇数的时候`F[i]=s[i+1];`当i为偶数的时候`F[i]=s[i+1]-1;` 72 | `s[i+1]=((-1)^i+2^(i+1))/3;` 73 | 接下来的求解过程就只需要注意边计算边取模就可以了。 74 | 75 | 注意:在对除数取余的时候为了防止精度的丢失,需要用到乘法的逆元,将除法变成乘法。这里使用的乘法逆元算法为费马小定理。 76 | 77 | --- 78 | 79 | #### **费马小定理** #### 80 | 在p是素数的情况下,对任意整数x都有`x^p≡x(mod)p`。 81 | 可以在p为素数的情况下求出一个数的逆元,`x∗x^(p−2)≡1(mod p)`,`x^(p−2)`即为逆元。 82 | ```cpp 83 | ll mult(ll a,ll n) //求a^n%mod 84 | { 85 | ll s=1; 86 | while(n) 87 | { 88 | if(n&1)s=s*a%mod; 89 | a=a*a%mod; 90 | n>>=1; 91 | } 92 | return s; 93 | } //mult(a,n-2); 94 | 95 | ``` 96 | 97 | --- 98 | 99 | ### 代码部分: ### 100 | 101 | ```cpp 102 | #include 103 | #include 104 | #include 105 | #include 106 | #include 107 | #include 108 | using namespace std; 109 | 110 | typedef __int64 LL; 111 | const int mod = 1e9+7; 112 | LL n,m; 113 | LL mult(LL a,LL n) 114 | { 115 | LL ans=1; 116 | while(n) 117 | { 118 | if(n&1)ans=(ans*a)%mod; 119 | a=(a*a)%mod; 120 | n>>=1; 121 | } 122 | return ans; 123 | } 124 | 125 | void solve() 126 | { 127 | if(m==1) 128 | { 129 | cout<<"1"<>T; 156 | while(T--) 157 | { 158 | cin>>n>>m; 159 | solve(); 160 | } 161 | return 0; 162 | } 163 | ``` 164 | 165 | >源代码:[**千千**](https://www.dreamwings.cn/hdu6050/4839.html) 166 | 167 | --- 168 | #### 最后讲一个`cin`的小技巧 #### 169 | `ios::sync_with_stdio(false);` 170 | 可以将cin和scanf的效率相匹敌。 171 | 详情说明: 172 | scanf在Linux平台上测试结果为2.01秒 173 | ```cpp 174 | const int MAXN = 10000000; 175 | int numbers[MAXN]; 176 | void scanf_read() 177 | { 178 | freopen("data.txt","r",stdin); 179 | for (int i=0;i> numbers[i]; 193 | } 194 | ``` 195 | cin慢是有原因的,其实默认的时候,cin与stdin总是保持同步的,也就是说这两种方法可以混用,而不必担心文件指针混乱,同时cout和stdout也一样,两者混用不会输出顺序错乱。正因为这个兼容性的特性,导致cin有许多额外的开销,如何禁用这个特性呢?只需一个语句`std::ios::sync_with_stdio(false);`,这样就可以取消cin于stdin的同步了。 196 | 197 | ```cpp 198 | const int MAXN = 10000000; 199 | 200 | int numbers[MAXN]; 201 | 202 | void cin_read_nosync() 203 | { 204 | freopen("data.txt","r",stdin); 205 | std::ios::sync_with_stdio(false); 206 | for (int i=0;i> numbers[i]; 208 | } 209 | ``` 210 | **取消同步后效率究竟如何?经测试运行时间锐减到了2.05秒,与scanf效率相差无几了!有了这个以后可以放心使用cin和cout了。** 211 | 212 | [toc] -------------------------------------------------------------------------------- /HDU_6052 To my boyfriend 【模拟】.md: -------------------------------------------------------------------------------- 1 | HDU_6052 To my boyfriend 【模拟】 2 | 3 | 4 | > [题目链接](http://acm.hdu.edu.cn/showproblem.php?pid=6052) 5 | 6 | ### 题目意思 ### 7 | 给定一个m*n的矩阵,矩阵的每一个单元上都有一种颜色,子矩阵的权值为子矩阵中不同颜色的数量,最后让你求出子矩阵权值之和除以所有的子矩阵之和。最后保留九位小数。 8 | ### 解题思路 ### 9 | 首先,求`m*n`的矩阵总共可以构成多少种子矩阵。这是有公式可以直接求出来的。公式:`n*(n+1)m(m+1)/*4 ` 10 | 接着难点就是来求不同颜色的矩形。 11 | 可以把每种颜色的每个点进行单独考虑,考虑过之后会将其标记,不会算入其他的子矩阵之中。考虑的过程是把考虑的点判断除标记点外可以构成多少个,把这种矩阵加起来就可以表示所有的子矩阵之和。 12 | #### 分析测试数据 #### 13 | 测试数据矩阵如下图: 14 | ![](http://115.159.67.147/wp-content/uploads/2017/07/1-1.png) 15 | 16 | - 首先先考虑颜色为1的点。先考虑坐标为(1,1)的点。可以构成子矩阵的数量为`1*2*3`。为6。测完以后将其标记以防重复。 17 | - 第二个点(3,1)。构成矩阵的数量为`2*2*1`。为4。测完同标记 18 | - 第三个点(2,2)。构成矩阵的数量为`2*2*1+1*1*1`。为5。颜色为1的点找完以后开始找颜色为二的点。 19 | - 然后是颜色为2的点。先考虑坐标为(1,2)的点。可以构成子矩阵的数量为`2*2*2`。为8.测完以后记性标记。 20 | - 第二个点(2,1)。数量`1*3*1+1*1*1=4`,标记。 21 | - 第三个点(2,3)。数量`1*2*1+1*1*1=3`,最后结束。 22 | 23 | 最后的结果为:`6+4+5+8+4+3=30`,总的子矩阵个数为`2*3*3*4/4`。最后的结果为`30/18`保留9位小数。 24 | 具体的计算步骤可以参考代码分析。 25 | 26 | ### 代码部分 ### 27 | 28 | ```cpp 29 | #include 30 | using namespace std; 31 | typedef long long LL; 32 | LL w[105][105];//保存原理图 33 | int vis[105],m,n;//vis[j] = i 表示将第j列用i颜色标记 34 | vector y[105];//y[i]用来表示第i行有哪些列被标记过 35 | vector > C[10005];//用来保存所有颜色出现的坐标 36 | LL calc(int col) 37 | { 38 | memset(vis,0,sizeof(vis)); 39 | LL sum = 0; 40 | for(auto now:C[col]) 41 | { 42 | int ni = now.first,nj = now.second;//保存当前颜色的坐标 43 | for(int i = 1; i <= m; i++)//对于每次找那些坐标被标记过之间都应该将此数组清零,防止以前数据对这次查找有影响 44 | y[i].clear(); 45 | for(int i=1; i<=m; i++) 46 | if(vis[i]) 47 | y[vis[i]].push_back(i); 48 | int yl = 1,yr = m,flag = 0; 49 | for(int i = ni; i>=1; i--) 50 | { 51 | //枚举所有可能的上边界 52 | vector::iterator it; 53 | for(it = y[i].begin(); it!=y[i].end(); it++) 54 | { 55 | //如果第i行有被标记的,找出被标记的列数,并更新左右区间 56 | int yy = *it; 57 | if(yy nj) 60 | yr = min(yr,yy-1); 61 | else 62 | { 63 | //如果这个点的正上方有与它颜色相同的点结束循环 64 | flag = 1; 65 | break; 66 | } 67 | } 68 | if(flag) break; 69 | sum += (n-ni+1)*(nj-yl+1)*(yr-nj+1);//计算已ii上上边界的方案数, 70 | } 71 | vis[nj] = ni;//将第nj列标记为ni方边利用y数组更新边界 72 | } 73 | return sum; 74 | } 75 | int main() 76 | { 77 | int T; 78 | scanf("%d",&T); 79 | while(T--) 80 | { 81 | scanf("%d %d",&n,&m); 82 | for(int i=0; i<=n*m; i++) 83 | C[i].clear(); 84 | for(int i = 1; i <= n; i++) 85 | for(int j = 1; j <= m; j++) 86 | { 87 | scanf("%lld",&w[i][j]); 88 | C[w[i][j]].push_back(make_pair(i,j));//记录每种颜色的坐标 89 | } 90 | LL sum = 0;//记录不同颜色子矩阵的个数 91 | for(int i=0; i<=n*m; i++) 92 | if(!C[i].empty()) 93 | { 94 | sort(C[i].begin(),C[i].end());//将每种颜色按坐标先行优先再列优先排序 95 | sum += calc(i); 96 | } 97 | LL num = n*(n+1)*m*(m+1)/4;//子矩阵的个数 98 | double ans = (sum*1.0)/num; 99 | printf("%.9lf\n",ans); 100 | } 101 | return 0; 102 | } 103 | 104 | ``` 105 | 106 | 这里使用了`for(auto now:C[col])`,auto是 c++11 里面的自动类型推导。如果不知道auto做什么的,点击:[auto的用法](http://www.cnblogs.com/me115/p/4800777.html) 107 | 如果代码直接编译不了就可以将codeblocks进行修改 108 | Setting->Compiler-> 109 | ![](http://115.159.67.147/wp-content/uploads/2017/07/X5UJHG1M3RXYCPR_1XM.png) -------------------------------------------------------------------------------- /HDU_6055 Regular polygon 【模拟】.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | > [题目链接](http://acm.hdu.edu.cn/showproblem.php?pid=6055) 4 | 5 | ### 题目意思 ### 6 | 二维平面给出n点,求可以构成多少正多边形。 7 | ### 解题思路 ### 8 | 由于要求正多边形,可以总结出,正多边形只能是正方形,这道题目就变成了给n个点求正方形有多少个。 9 | 这里就需要考虑怎么判断正方形是否存在。 10 | 我们将所有的点进行排序,然后所有的点两两进行组合,然后通过公式求出第三个和第四个点,判断是否在点的集合里面。判断是否在点的集合里面我这里是使用了二分查找的操作,当然也可以用一个标记数组,效率会更高一点。 11 | ### 代码部分 ### 12 | 13 | ```cpp 14 | #include 15 | using namespace std; 16 | const int maxn=510; 17 | struct Node 18 | { 19 | int x,y; 20 | bool operator<(const Node &rhs)const 21 | { 22 | if(x == rhs.x) 23 | return y < rhs.y; 24 | else 25 | return x < rhs.x; 26 | } 27 | } nodes[maxn]; 28 | 29 | int main() 30 | { 31 | int n; 32 | while(~scanf("%d",&n)) 33 | { 34 | int ans=0;//正方形个数 35 | for(int i=0; i [题目链接](http://acm.hdu.edu.cn/showproblem.php?pid=6058) 4 | 5 | 题目意思 6 | 给定n,k。n表示一个序列的个数,并且序列的值为1~n。求这个序列所有的子序列第k大值之和,序列顺序不可改变。 7 | 解题思路 8 | 一开始以为是主席树,也是类似求在不改变序列的条件下,求区间的第k大值,然后就是把所有情况都加一遍。 9 | 我们只要求出对于一个数x左边最近的k个比他大的和右边最近k个比他大的,扫一下就可以知道有几个区间的k大值是x。 10 | 11 | 我们考虑从小到大枚举x,每次维护一个链表,链表里只有>=x的数,那么往左往右找只要暴力跳k次,删除也是O(1)的。 12 | 13 | 时间复杂度:O(nk) 14 | 代码部分 15 | ```cpp 16 | #include 17 | using namespace std; 18 | typedef long long ll; 19 | 20 | const int N=5e5+7; 21 | int t,n,k,a[N],idx[N];///a表示的是这个数组,idx表示的是某个数在的位置 22 | struct Node 23 | { 24 | int pre,nxt,idx; 25 | } node[N]; 26 | 27 | int main() 28 | { 29 | scanf("%d",&t); 30 | while(t--) 31 | { 32 | scanf("%d%d",&n,&k); 33 | for(int i=1;i<=n;i++) 34 | { 35 | scanf("%d",&a[i]),idx[a[i]]=i; 36 | node[i]=Node {i-1,i+1,i}; 37 | } 38 | node[n+1].idx=n+1; 39 | ll ans=0; 40 | for(int i=1;i<=n;i++)///找到当前这个数 41 | { 42 | int l=idx[i],r=idx[i];///左端点,右端点 43 | int cntl=1,cntr=0;///往前、往后找的数的个数 44 | while(cntlk)///左右区间的个数大于k了 52 | { 53 | cntr--,r=node[r].pre;///右区间往前移动 54 | } 55 | while(cntl+cntr 4 | 5 | > [题目链接](http://acm.hdu.edu.cn/showproblem.php?pid=6060) 6 | 7 | 题目描述 8 | 9 | 给一棵树T,有n个结点。 10 | 给一个k,表示有k个集合,我们需要把2,3,4,…n号节点放入集合,要保证k个集合的并集等于{2,3,4,5n},并且集合互不相交。(集合可以为空) 11 | 然后每次取一个集合Si与{1}求并,得到比如{1,2,3},那么tempi = f({1,2,3});f({1}并Si)的意思是把合内的所有点连接起来的边的权值和。最后把所有权值和相加的到答案。 12 | 最后问你能够得到最大的答案。 13 | 14 | 解题思路 15 | 16 | 我们要想得到最大的答案,那么就要尽可能的去利用这些边,也就是尽可能重复计算这些边。 17 | 那么我们想,假设先从叶子节点开始,把这些叶子节点放入一个集合,那么这个集合的temp值就会把所有的边都算一遍。那么下次我们取所有叶子节点的父亲,放入一个集合,那么这个集合的temp值会把除了叶子节点到父亲的那条那边的其他所有边都算一遍。因为集合可以为空,以此类推,我们就可以得到最大的答案。但是如果遇到集合不够的情况,就把剩下的所有点加入最后一个集合。 18 | 那么有以上分析,其实就是算每条边会算多少次,比如叶子节点到父亲的那条边会算一次。其实一条边会算多少次跟某个点的所有子孙节点个数有关,就比如样例中,2号点有3个子孙节点,那么2号点连接父节点的那条边会算3+1次。3号点有0个子孙节点,那么3号点连接父节点的那条边会算0+1次。 19 | 那么其实问题就是转化为求每个点的子孙节点个数,然后算出每条边要重复计算的次数即可。 20 | 21 | 代码部分 22 | 23 | ```cpp 24 | #include 25 | using namespace std; 26 | 27 | struct Edge{ 28 | intv,w; 29 | }; 30 | 31 | Edge temp; 32 | vectorvec[1000005]; 33 | int Size[1000005]; ///保存每个节点的子节点的个数 34 | int w[1000005]; ///存权重 35 | 36 | void dfs(intu,int pre){ ///递归找到每一个节点的子节点的个数 37 | Size[u]=1; 38 | intlen=vec[u].size(); 39 | for(inti=0;i 3 | 4 | > [题目链接](http://acm.hdu.edu.cn/showproblem.php?pid=6063) 5 | 6 | ### 题目描述 ### 7 | 乱七八糟的莫比乌斯公式,最后迷迷糊糊,答案就是 $$n^k$$ 8 | ### 解题思路 ### 9 | 签到题,数值较大,需要用到整数快速幂,注意大数取模问题 10 | ### 代码部分 ### 11 | ```cpp 12 | #include 13 | using namespace std; 14 | 15 | typedef long long LL; 16 | const int mod = 1e9+7; 17 | LL Integer_Quick_Pow(LL n,LL k)//取模过程需要注意,在每次相乘都要对mod取模 18 | { 19 | LL ans,res; 20 | ans = 1; 21 | res = n; 22 | while(k) 23 | { 24 | if(k&1) 25 | ans = ((ans%mod)*(res%mod))%mod; 26 | res = ((res%mod)*(res%mod))%mod; 27 | k = k>>1; 28 | } 29 | return ans; 30 | } 31 | int main() 32 | { 33 | LL n,k; 34 | int Case=0; 35 | while(~scanf("%lld%lld",&n,&k)) 36 | { 37 | printf("Case #%d: %lld\n",++Case,Integer_Quick_Pow(n,k)); 38 | } 39 | return 0; 40 | } 41 | ``` -------------------------------------------------------------------------------- /HDU_6069 Counting Divisors 【数论】.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | > [题目链接](http://acm.hdu.edu.cn/showproblem.php?pid=6069) 5 | 6 | ### 题目描述 ### 7 | 8 | ![](http://115.159.67.147/wp-content/uploads/2017/08/d.png) 9 | 10 | 简单来说求区间因数个数和。 11 | ### 解题思路 ### 12 | 首先我们先要知道数论之中的一个很重要的知识点。 13 | **数的因子个数** 14 | `n = p1^a1 * p2^a2 * p3^a3 …… *pn^an` 15 | 其中p1,p2,p3……均为素数。n的因数个数为`(a1+1)*(a2+1)*(a2+1)*(a3+1)……*(an+1)` 16 | 如果n加一个次方 n^k的因子个数为`(a1*k+1)*(a2*k+1)*(a2*k+1)*(a3*k+1)……*(an*k+1)` 17 | 然后这道题就是让算一个区间的K次方的因数个数之和,首先先将`1e6`之内的素数打表,然后再然后枚举这些素数,在枚举 [L,R] 区间中这些素数的倍数,然后根据这些倍数进行素因子分解,其实就是统计 [L,R] 区间中有多少个枚举的素数,然后乘以 K, 在加 1 计算即可。在枚举 [L,R] 区间值的时候,我们需要先把 [L,R] 区间中的数用数组保存下来,然后通过数组进行素因子分解。 18 | ### 代码部分 ### 19 | 20 | ```cpp 21 | #include 22 | using namespace std; 23 | typedef long long LL; 24 | const LL MOD = 998244353; 25 | const LL MAXN = 1e6+5; 26 | int prime[MAXN], cnt;//cnt代表1e6中素数的个数 27 | LL p[MAXN];//存放素数从0开始 28 | ///筛法求素数 29 | void isprime() 30 | { 31 | memset(prime, 0, sizeof(prime)); 32 | cnt = 0; 33 | prime[1] = 1; 34 | for(LL i=2; i 2 | 3 | > [题目链接](http://acm.hdu.edu.cn/showproblem.php?pid=6070) 4 | 5 | ### 题目描述 ### 6 | 已知一个区间的不同题目个数/区间题目个数为这个区间的正确率。给定一个序列,求序列的正确率最小为多少。 7 | ### 解题思路 ### 8 | 我们求AC率,AC无非就是0~1间的一个数,因此采用二分答案的方法求解。 9 | 现在假设答案为mid.则`distinct【left,right】/(right-left+1) <= mid` ,则说明答案是小于mid的。上面的式子可以变成下面的式子。 10 | `distinct【left,right】 <= mid*(right-left+1)` 11 | `distinct【left,right】+ mid*left <= mid*(right+1)` 12 | 因此确定一个mid值,我们构建一颗空的线段树,每个节点的值先赋值为`mid*left`,其中left为当前节点的左边界,对于线段树每个节点我们存放的是`distinct【left,right】+mid*left`,对于这些节点,我们维护的是节点所代表区间的最小值,因为固定右边界的时候,右侧`mid*(right+1)`是固定的,我们需要让左侧尽量小。 13 | 14 | 那么需要更新的就是`distinct【left,right】`区间中的值。我们可以通过遍历右边界,数组中的每个数都可以作为右边界,对于新加入线段树的a[i]。pre[a[i]]记录前一次a[i]这个数字出现的位置,因此我们知道在这次遇到a[i]之前,pre[a[i]]+1 ~ i,这些位置都没有出现过a[i]这个数字,所以在线段树中区间pre[a[i]]~i,应该加上1,就是a[i]这个数字。 15 | 16 | 更新完后,我们就可以查询【1,i】这个区间,原来我比较不理解这里,后来想明白了,1~i,这个区间求解的时候,这个区间包含许多子区间,例如【2,i】,【3,i】,【4,i】~【i,i】。这样的话每次插入第i个数,相当于从这些区间中求了一个`distinct【left,right】+mid*left` 的最小值。在循环过程中,只要有一个值小于`mid*(right+1)`,就说明我们假设的值偏大了,循环完了也没出现这种情况说明假设的值偏小了。 17 | 18 | #### 强调两点线段树中的操作: #### 19 | 20 | - push_up操作,即更新当前节点的值。我们要维护线段树区间最小值,此函数主要用于建树过程和更新过程,由于建树过程是个递归过程,当建完当前节点的左右子树的时候,当前节点的最小值就是左右子树节点中的最小值。当进行区间更新操作的时候,由于整个区间都加上的一个相同的值,则区间的值就改变了,则其父母的值都需要进行更新,因此更新完左右子树后,就要跟新当前节点的值。 21 | 22 | - push_down操作,下推懒惰标记,主要用于更新操作和查询操作,当整个区间加上同一个值的时候,我们知道其子区间也同样需要加上相应的值,懒惰标记就是lazy【】数组,里面存放着某个节点所代表的区间要加上的值。 23 | 24 | **注意:**精度问题,二分次数越多,答案越接近正确答案,但也不能过高,过高可能超时,具体二分次数,差不多试一下就可以试出来。 25 | 26 | ### 代码部分 ### 27 | 28 | ```cpp 29 | #include 30 | #include 31 | #include 32 | #include 33 | #define eps 1e-6 34 | #define inf 0x3f3f3f3f 35 | #define lchild left,mid,root<<1 36 | #define rchild mid+1,right,root<<1|1 37 | using namespace std; 38 | 39 | const int maxn = 60010; 40 | int a[maxn]; 41 | double Min[maxn<<2]; ///线段树节点值 42 | double lazy[maxn<<2]; ///懒惰标记数组 43 | int pre[maxn]; ///pre[i]用来存放i这个数上一次出现的时候在数组中的位置 44 | ///更新当前节点 45 | void push_up(int root) 46 | { 47 | Min[root] = min(Min[root<<1],Min[root<<1|1]); 48 | } 49 | ///懒惰标记下推 50 | void push_down(int root) 51 | { 52 | if(lazy[root]>eps) 53 | { 54 | Min[root<<1] += lazy[root]; 55 | lazy[root<<1] += lazy[root]; 56 | Min[root<<1|1] += lazy[root]; 57 | lazy[root<<1|1] += lazy[root]; 58 | lazy[root] = 0; 59 | } 60 | } 61 | ///构建线段树 62 | void build(int left,int right,int root,double temp) 63 | { 64 | Min[root] = left*temp; 65 | lazy[root] = 0; 66 | if(left == right) return; 67 | int mid = (left+right)>>1; 68 | build(lchild,temp); 69 | build(rchild,temp); 70 | push_up(root); 71 | } 72 | ///区间更新,同时加上add 73 | void update(int L,int R,int add,int left,int right,int root) 74 | { 75 | if(L<=left && right<=R) 76 | { 77 | Min[root] += add; 78 | lazy[root] += add; 79 | return; 80 | } 81 | ///如果父区间都加上了add,则其子区间也要加上add,所以要下推懒惰标记。 82 | push_down(root); 83 | int mid = (left+right)>>1; 84 | ///更新左右子树。 85 | if(L<=mid) update(L,R,add,lchild); 86 | if(R>mid) update(L,R,add,rchild); 87 | push_up(root); 88 | } 89 | ///区间查询操作 90 | double query(int L,int R,int left,int right,int root) 91 | { 92 | if(L<=left && right<=R) 93 | return Min[root]; 94 | push_down(root); ///避免要查询的区间有懒惰标记没有下推。 95 | double ans = inf*1.0; 96 | int mid = (left+right)>>1; 97 | if(L<=mid) ans = min(ans,query(L,R,lchild)); 98 | if(R>mid) ans = min(ans,query(L,R,rchild)); 99 | return ans; 100 | } 101 | ///判读二分答案偏大,偏小。 102 | bool check(int n,double temp) 103 | { 104 | build(1,n,1,temp); 105 | memset(pre,0,sizeof(pre)); 106 | for(int i = 1; i <= n; i++) 107 | { 108 | update(pre[a[i]]+1,i,1,1,n,1); 109 | double minimum = query(1,i,1,n,1); 110 | pre[a[i]] = i; 111 | if(temp*(i+1)-minimum >= eps) 112 | return true; 113 | } 114 | return false; 115 | } 116 | int main() 117 | { 118 | int t,n; 119 | scanf("%d",&t); 120 | while(t--) 121 | { 122 | scanf("%d",&n); 123 | for(int i = 1; i <= n; i++) 124 | scanf("%d",&a[i]); 125 | double L,R,mid,ans; 126 | L = 0; 127 | R = 1; 128 | for(int i = 0; i < 30; i++) 129 | { 130 | mid = (L+R)/2.0; 131 | if(check(n,mid)) R=ans=mid; 132 | else L=ans=mid; 133 | } 134 | printf("%.10lf\n",ans); 135 | } 136 | return 0; 137 | 138 | } 139 | ``` 140 | 141 | > 转自 [**温姑娘的博客**](http://blog.csdn.net/wyxeainn/article/details/76687575) -------------------------------------------------------------------------------- /HDU_6078 Wavel Sequence 【dp】.md: -------------------------------------------------------------------------------- 1 | HDU_6078 Wavel Sequence 【dp】 2 | 3 | 4 | > [题目链接](http://acm.hdu.edu.cn/showproblem.php?pid=6078) 5 | 6 | ### 题目描述 ### 7 | 阅读理解般的题目。。。给定两个序列a,b,a序列需要满足波浪形的序列,即`a1a3a5 15 | #define CLR(A, X) memset(A, X, sizeof(A)) 16 | using namespace std; 17 | 18 | typedef long long LL; 19 | 20 | const LL MOD = 998244353; 21 | const int N = 2e3+5; 22 | 23 | int b[N], a[N]; 24 | LL sum[N][2], dp[N][2]; 25 | 26 | int main() 27 | { 28 | 29 | int T, n, m; 30 | scanf("%d", &T); 31 | while(T--) 32 | { 33 | CLR(sum, 0); 34 | CLR(dp, 0); 35 | scanf("%d%d", &n, &m); 36 | for(int i = 1; i <= n; i++) scanf("%d", &a[i]); 37 | for(int i = 1; i <= m; i++) scanf("%d", &b[i]); 38 | LL ans = 0; 39 | for(int i = 1; i <= n; i++) 40 | { 41 | LL cnt1 = 1, cnt0 = 0; 42 | for(int j = 1; j <= m; j++) 43 | { 44 | dp[j][0] = dp[j][1] = 0; 45 | if(a[i] == b[j]) 46 | { 47 | dp[j][0] = cnt1; 48 | dp[j][1] = cnt0; 49 | ans = (ans+cnt1+cnt0)%MOD; 50 | } 51 | else if(b[j] < a[i]) (cnt0 += sum[j][0]) %= MOD; 52 | else (cnt1 += sum[j][1]) %= MOD; 53 | } 54 | for(int j = 1; j <= m; j++) 55 | { 56 | if(a[i] == b[j]) 57 | { 58 | (sum[j][0] += dp[j][0]) %= MOD; 59 | (sum[j][1] += dp[j][1]) %= MOD; 60 | } 61 | } 62 | } 63 | printf("%lld\n", ans); 64 | } 65 | return 0; 66 | } 67 | 68 | 69 | ``` -------------------------------------------------------------------------------- /HDU_6090 Rikka with Graph 【贪心】.md: -------------------------------------------------------------------------------- 1 | HDU_6090 Rikka with Graph 【贪心】 2 | 3 | 4 | > [题目链接](http://acm.hdu.edu.cn/showproblem.php?pid=6090) 5 | 6 | ### 题目描述 ### 7 | 给定n个点,m条边,让你安排点和边构成一个无向图。dis(i,j),表示i到j最小的边数,如果无法到达,dis(i,j)为n,问每个点到其他所有的点的dis之和。 8 | ### 解题思路 ### 9 | 贪心思想,构图的时候一个点当中点,其他的点都围绕在周围,有多少个边连多少个点。 10 | 分情况考虑。 11 | - 当任意两点都联通,也就是$$ `m >= n*(n-1)/2` $$。得到的答案就直接为$$ `n*(n-1)` $$ 12 | - 再有就是将 m = n - 1 为边界,分情况考虑即可 13 | - 当 m≤n−1 时,我们需要最小化距离为 n 的点对数,所以肯定是连出一个大小为 m+1 的联通块,剩下的点都是孤立点。在这个联通块中,为了最小化内部的距离和,肯定是连成一个菊花的形状,即一个点和剩下所有点直接相邻。 14 | - 当 m>n−1 时,肯定先用最开始 n−1 条边连成一个菊花,这时任意两点之间距离的最大值是 2。因此剩下的每一条边唯一的作用就是将一对点的距离缩减为 1。 15 | 16 | ### 代码部分 ### 17 | ```cpp 18 | #include 19 | using namespace std; 20 | typedef long long ll; 21 | int main() 22 | { 23 | ios::sync_with_stdio(false); 24 | int t; 25 | cin >> t; 26 | while(t--) 27 | { 28 | ll n, m, ans; 29 | cin >> n >> m; 30 | ans = (n - 1) * (n - 1) * 2; 31 | if(m >= n * (n - 1) / 2) {cout << n * (n - 1) << endl; continue;}//当每个点都可以相互链接时,距离和直接为 n * (n - 1) 32 | if(m < n - 1) cout << 2 * m * m + n * (n + m) * (n - m - 1) << endl; //另一种情况就是以m = n - 1 为边界考虑 33 | else 34 | { 35 | ll cnt = m - n + 1; 36 | cout << ans - cnt * 2 << endl; 37 | } 38 | } 39 | return 0; 40 | } 41 | ``` -------------------------------------------------------------------------------- /HDU_6092 Rikka with Subset 【dp】.md: -------------------------------------------------------------------------------- 1 | HDU_6092 Rikka with Subset 【dp】 2 | 3 | 4 | > [原题链接](http://acm.hdu.edu.cn/showproblem.php?pid=6092) 5 | 6 | ### 题目描述 ### 7 | 有一个数列 a[] ,长度(n<=50)。b[i] 表示元素和为i的集合个数。给你一个数列 b[] ,长度(m<=10000),让你求 a[],并按照其字典序最小输出。 8 | ### 解题思路 ### 9 | 设想a子集中肯定会有一个集合为空集,所以b[0],为空集的个数。那么除了空集以外b[i]如果不为零,则i为a集合中最小的值,并想办法将其在b集合中去除。 10 | 这里用了dp的思想,从i下表开始之后的动态转移方程 `b[i] -= b[i - w]` w为最小的不为零的b数组的下标。 11 | ### 代码部分 ### 12 | ```cpp 13 | #include 14 | using namespace std; 15 | const int maxn = 1e4 + 10; 16 | long long b[maxn]; 17 | int m, n; 18 | int main() 19 | { 20 | ios::sync_with_stdio(false); 21 | int t; 22 | cin >> t; 23 | while(t--) 24 | { 25 | cin >> n >> m; 26 | for(int i = 0; i <= m ; i++) cin >> b[i]; 27 | while(n--) 28 | { 29 | int w; 30 | for(int i = 1; i <= m; i++) if(b[i]) {w = i, cout << w; break;} 31 | if(n) cout << " "; 32 | for(int i = w; i <= m; i++) b[i] -= b[i - w]; 33 | } 34 | cout << endl; 35 | } 36 | return 0; 37 | } 38 | 39 | ``` -------------------------------------------------------------------------------- /HDU_6096 String 【字典树】.md: -------------------------------------------------------------------------------- 1 | HDU_6096 String 【字典树】 2 | 3 | 4 | > [题目链接](http://acm.hdu.edu.cn/showproblem.php?pid=6096) 5 | 6 | ### 题目描述 ### 7 | 给定n个字符串,给m个前缀后缀,问这样的前缀后缀在字符串中出现的次数。 8 | ### 解题思路 ### 9 | 字典树问题,静态数组构建字典树。动态会爆TLE。 10 | **建树步骤:**首先说一下树的数据结构:cnt:表示这样的前缀有多少种。nu数组:存放这样的前后缀有匹配的字符长度,作用就是排除前后缀长度大于字符串长度。接着就是常规的nex二维数组,建树专用。然后再说一下字符串的处理。给定字符串 如cde -> ceddec ,两个指针分别指向字符的首和尾然后一个向前,一个向后延伸。这样建树可以对给定的前缀后缀处理后就可以直接查询。 11 | **对前缀后缀处理:**和建树类似,也是首尾两两结合,如不足补`'*'`如 `ce f -> cfe*`。 12 | 这样对字符处理后,就变成普通的查询了。注意如果再查询中遇到了'*',需要对该节点的所有情况都考虑一下。 13 | ### 难点解析 ### 14 | 15 | - 给处理后的字符可能现字符长度小于前后缀的长度和,这种情况是不符合的。 16 | - 对遇到`*`号的处理。需要把`*`的所有情况的进行考虑。 17 | 18 | ### 代码部分 ### 19 | ```cpp 20 | #include 21 | using namespace std; 22 | const int maxn = 5e5 + 10; 23 | int nex[maxn << 1][26];//用来构建字典树,表明下个节点 24 | int cnt[maxn << 1]; // 用来存放该前缀后缀经过的次数。 25 | vector nu[maxn << 1]; //存该前缀后缀的字符长度。 26 | int top, root; 27 | /// 字母转换为数字 28 | inline int idx(char c) 29 | { 30 | return c - 'a'; 31 | } 32 | ///建树需要转换的字符串 33 | inline string Idx(string c) 34 | { 35 | string s = ""; 36 | for(int i = 0, j = c.size() - 1; i < c.size(); ++ i, -- j) 37 | { 38 | char c1 = c[i], c2 = c[j]; 39 | s += c1, s += c2; 40 | } 41 | return s; 42 | } 43 | ///查前后缀需要转换的字符串 44 | inline string IDX(string c1, string c2) 45 | { 46 | string s = ""; 47 | int len1 = c1.size(), len2 = c2.size(); 48 | reverse(c2.begin(), c2.end()); 49 | int len = max(len1, len2); 50 | for(int i = 0; i < len; ++ i) 51 | { 52 | char c; 53 | if(i + 1 > len1) 54 | s += '*'; 55 | else 56 | c = c1[i], s += c; 57 | if(i + 1 > len2) 58 | s += '*'; 59 | else 60 | c = c2[i], s += c; 61 | } 62 | return s; 63 | } 64 | ///添加字典树节点 65 | inline int CreatTrie() 66 | { 67 | for(int i = 0; i < 26; ++ i) nex[top][i] = 0; 68 | cnt[top] = 0; 69 | return top ++; 70 | } 71 | ///初始化字典树 72 | inline void Init() 73 | { 74 | top = 0; 75 | memset(nex, 0, sizeof(nex)); 76 | for(int i = 0; i < 2 * maxn; ++ i) nu[i].clear(); 77 | root = CreatTrie(); 78 | } 79 | ///插入字符串建树 80 | void Insert(string c) 81 | { 82 | int len = c.size(); 83 | int now = root; 84 | for(int i = 0; i < len; ++ i) 85 | { 86 | int temp = idx(c[i]); 87 | if(!nex[now][temp]) 88 | { 89 | nex[now][temp] = CreatTrie(); 90 | } 91 | now = nex[now][temp]; 92 | nu[now].push_back(len / 2); 93 | cnt[now] ++; 94 | } 95 | } 96 | ///对节点所包含的字符长度进行排序 97 | void dfs(int x) 98 | { 99 | sort(nu[x].begin(),nu[x].end()); 100 | for(int i = 0; i < 26; ++ i) 101 | if(nex[x][i]) dfs(nex[x][i]); 102 | } 103 | int main() 104 | { 105 | ios::sync_with_stdio(false); 106 | int t; 107 | cin >> t; 108 | while(t --) 109 | { 110 | int m, n; 111 | string s; 112 | cin >> m >> n; 113 | Init(); 114 | for(int i = 1; i <= m; ++ i) 115 | { 116 | cin >> s; 117 | Insert(Idx(s)); 118 | } 119 | dfs(0); 120 | while(n --) 121 | { 122 | string s1, s2; 123 | cin >> s1 >> s2; 124 | int ret = s1.size() + s2.size(); 125 | s = IDX(s1, s2); 126 | queue Q[2]; 127 | int tmp = 0; 128 | Q[tmp].push(0); 129 | int ans = 0; 130 | for(int i = 0; i < s.size(); ++ i) 131 | { 132 | tmp = 1-tmp; 133 | int id = idx(s[i]); 134 | while(!Q[1-tmp].empty()) 135 | { 136 | int now = Q[1-tmp].front(); 137 | Q[1-tmp].pop(); 138 | if(s[i] == '*') 139 | { 140 | for(int j = 0; j < 26; ++ j) 141 | { 142 | if(nex[now][j]) 143 | { 144 | Q[tmp].push(nex[now][j]); 145 | } 146 | } 147 | } 148 | else 149 | { 150 | if(nex[now][id]) 151 | { 152 | Q[tmp].push(nex[now][id]); 153 | } 154 | } 155 | } 156 | } 157 | while(!Q[tmp].empty()) 158 | { 159 | int now = Q[tmp].front(); 160 | Q[tmp].pop(); 161 | int temp = lower_bound(nu[now].begin(),nu[now].end(),ret) - nu[now].begin(); 162 | ans -= temp; 163 | ans += cnt[now]; 164 | } 165 | printf("%d\n",ans); 166 | } 167 | } 168 | return 0; 169 | } 170 | 171 | ``` -------------------------------------------------------------------------------- /HDU_6097 Mindis 【几何】.md: -------------------------------------------------------------------------------- 1 | HDU_6097 Mindis 【几何】 2 | 3 | 4 | > [题目链接](http://acm.hdu.edu.cn/showproblem.php?pid=6097) 5 | 6 | ### 题目描述 ### 7 | 给定一个圆心在坐标原点的圆的半径,然后给出两个到该圆的圆心等长的非圆外点P,Q(点既可 以在圆内,也可以在圆上),让从圆上找一点D使其到两点的距离最短,即DP+DQ最小。 8 | ### 解题思路 ### 9 | 请况分为以下三种: 10 | 11 | - P点和Q点重合,这时直接输出2*(R-OP); 12 | - OP=OQ=R; 这时的最多短距离就是P Q的距离了。 13 | - OP=OQ < R;这种情况比较麻烦,首先我们要明白一个概念反演点。 14 | 反演点:已知圆O的半径为R,从圆心O出发任作一射线,在射线上任取两点M,N。若OM=m,ON=n,且mn=R^2,则称点M,N是关于圆O的反演点。 15 | 16 | ![](http://www.adreame.com/wp-content/uploads/2017/08/几何1.png) 17 | 18 | >由图可知: 19 | OQ×OQ1 =r×r=|OD|² 20 | 易知△ODQ∽△ODQ1 △ODP∽△ODP1 21 | 故可以推出OP/OD=DP/DP1 OQ/OD=DQ/DQ1 22 | 两式结合DP+DQ=(OP/OD) * DP1+(OQ/OD) * DQ1 23 | 又∵OP =OQ 24 | ∴DP+DQ=OP/OD(DP1+DQ1) 25 | 所以我们只需要求出(DP1+DQ1)的值即可 26 | 27 | ![](http://www.adreame.com/wp-content/uploads/2017/08/几何2.png) 28 | 29 | 上图为od1>r的情况,第一个是od1<=r的情况。 30 | 31 | ### 代码部分 ### 32 | ```cpp 33 | #include 34 | 35 | using namespace std; 36 | double dis(double x1,double y1,double x2,double y2) 37 | { 38 | double d = sqrt((x2-x1)*(x2-x1)+(y2-y1)*(y2-y1)); 39 | return d; 40 | } 41 | int main() 42 | { 43 | int t;scanf("%d",&t); 44 | while(t--) 45 | { 46 | double px,py,qx,qy,r; 47 | scanf("%lf %lf %lf %lf %lf",&r,&px,&py,&qx,&qy); 48 | if(px == qx&&py == qy){///第一种情况 49 | printf("%.7lf\n",2.0*(r - dis(0.0,0.0,px,py))); 50 | } 51 | else if(dis(0.0,0.0,px,py) == r){///第二种情况 52 | printf("%.7lf\n",dis(px,py,qx,qy)); 53 | } 54 | else{///第三种情况 55 | double op = dis(px,py,0.0,0.0); 56 | double a = acos((px*qx+py*qy)/(op*op))/2.0 ;///利用反三角函数求出角POD 57 | double op1 = r*r/op; 58 | double od1 = op1*cos(a);///利用角POD求出O点到距离od1 59 | if(od1<=r) 60 | { 61 | printf("%.7lf\n",2.0*op1*sin(a)*(op/r));///若距离小于半径,则最短距离为p1q1*(op/r) 62 | } 63 | else{ 64 | double dd1 = od1 - r; 65 | double dp1 = sqrt(dd1*dd1 + (op1*sin(a))*(op1*sin(a)));///否则计算出dp1的距离dq1 = dp1 66 | printf("%.7lf\n",2.0*dp1*op/r); 67 | } 68 | } 69 | } 70 | return 0; 71 | } 72 | ``` 73 | 74 | > [原文链接](http://blog.csdn.net/merry_hj/article/details/77151650) -------------------------------------------------------------------------------- /HDU_6098 Inversion 【暴力】.md: -------------------------------------------------------------------------------- 1 | HDU_6098 Inversion 【暴力】 2 | 3 | 4 | 5 | > [题目链接](http://acm.hdu.edu.cn/showproblem.php?pid=6098) 6 | 7 | ### 题目描述 ### 8 | 首先先普及一下'a|b'代表a是b的因子,即b%a=0,'a∤b'也就是b%a!=0 9 | 给定一个序列,求i从2开始不是i倍数的序列中的最大值,依次输出 10 | ### 解题思路 ### 11 | 暴力大法好,首先用一个结构体数组保存值与下标。然后对值进行从大到小的排序,然后依此对每个下标i找出对i取余不为零的输出。 12 | ### 代码部分 ### 13 | ```cpp 14 | #include 15 | typedef long long ll; 16 | using namespace std; 17 | const int maxn = 1e5+10; 18 | struct node 19 | { 20 | ll m; 21 | int x; 22 | }a[maxn]; 23 | inline bool cmp(node a, node b) 24 | { 25 | return a.m > b.m; 26 | } 27 | int main() 28 | { 29 | int t; 30 | scanf("%d", &t); 31 | while(t--) 32 | { 33 | int n; 34 | ll maxn,mm; 35 | scanf("%d", &n); 36 | for(int i = 1; i <= n; i++) scanf("%lld", &mm),a[i].x = i,a[i].m = mm; 37 | maxn = 0; 38 | for(int i = 1; i <= n; i+=2) 39 | { 40 | maxn = max(maxn, a[i].m); 41 | } 42 | printf("%lld", maxn); 43 | sort(a + 1,a + n + 1, cmp); 44 | for(int i = 3; i <= n; i++) 45 | { 46 | for(int j = 1; j <= n; j++) 47 | { 48 | if(a[j].x % i != 0) {printf(" %lld",a[j].m);break;} 49 | } 50 | } 51 | printf("\n"); 52 | } 53 | return 0; 54 | } 55 | ``` -------------------------------------------------------------------------------- /HDU_6105 Gameia 【博弈】.md: -------------------------------------------------------------------------------- 1 | HDU_6105 Gameia 【博弈】 2 | 3 | 4 | > [题目链接](http://acm.hdu.edu.cn/showproblem.php?pid=6105) 5 | 6 | 7 | ### 题目描述 ### 8 | Alice和Bob玩一个游戏,一开始有一颗没有颜色的树,Bob和Alice分别对树进行染色,Alice每次将一个没有颜色的点涂成白色,Bob每次将一个没有颜色的点涂成黑色,并且可以将与涂上黑色的点直接相邻的点变为黑色,假如最后树上存在白色点,Alice赢,否则Bob赢。Bob还有一个特权,可以在任意时候,删除任意一条边。 9 | ### 解题思路 ### 10 | 只有当n为偶数且Bob可以根据他的特权将这棵树切成两两相连的时候,才可以获得胜利,否则都是Alice赢。 11 | ### 代码部分 ### 12 | ``` 13 | #include 14 | using namespace std; 15 | const int maxn = 1e3; 16 | int main() 17 | { 18 | ios::sync_with_stdio(false); 19 | int t; 20 | cin >> t; 21 | while(t --) 22 | { 23 | int n, k, fa[maxn], si[maxn]; 24 | bool flag = true; 25 | cin >> n >> k; 26 | for(int i = 2; i <= n; ++ i) cin >> fa[i]; 27 | for(int i = 1; i <= n; ++ i) si[i] = 1; 28 | for(int i = n; i >= 1; -- i) 29 | { 30 | if(si[i] >= 3) 31 | flag = false; 32 | si[fa[i]] += si[i] & 1; 33 | } 34 | if(flag && n % 2 == 0 && k >= n / 2 - 1) cout << "Bob" << endl; 35 | else cout << "Alice" << endl; 36 | } 37 | return 0; 38 | } 39 | 40 | ``` -------------------------------------------------------------------------------- /HDU_6121 Build a tree 【DFS&&位运算技巧】.md: -------------------------------------------------------------------------------- 1 | HDU_6121 Build a tree 【DFS&&位运算技巧】 2 | 3 | > [题目链接](http://acm.hdu.edu.cn/showproblem.php?pid=6121) 4 | 5 | ### 题目描述 ### 6 | 给定一个n, k。建一颗k叉树,有n个节点。这棵树节点的值代表这棵树子节点的个数加上本身的节点总数之和。求所有节点值的异或。 7 | Limit n和k都是 1e18 的范围。 8 | ### 解题思路 ### 9 | 根据数据的大小,可以知道肯定不是直接递归来求的答案。 10 | 分两种情况: 11 | 12 | - k为1的时候,为求 1 ~ n的异或,如果一个一个异或肯定超时,这里有个规律,见代码部分,函数可以直接求处1 ~ n 的异或值。 13 | - k > 1时。我们可以通过给的节点来创建树,不难发现,这棵树中只有一颗是一个不满的k叉树,这棵树左边都为满k叉树,右边的也是满k叉树,比右边的会少一层。这样我们就可以直接求出左右满k叉树的异或值然后再异或上不满足k叉树的那棵树即可。不满足k叉树的我们就可以递归进行计算。每次通过递归来计算不满k叉树的左右满k叉树的数量以及其异或值。因为当k大于1时,最多递归的次数也不会超过100次,所以时间肯定是够的。 14 | - 最后,奇数个n异或的值相当于n,偶数个n异或为0。 15 | 16 | ### 代码部分 ### 17 | ```cpp 18 | #include 19 | using namespace std; 20 | typedef long long ll; 21 | ///计算1 ~ n的 异或值 22 | ll ans; 23 | inline ll Xor(ll n) 24 | { 25 | return n%4==1?1:(n%4==2?n+1:(n%4==3?0:n)); 26 | } 27 | void dfs(ll n, ll k) 28 | { 29 | if(n - 1 <= k) 30 | { 31 | if(!(n & 1)) ans ^= 1; 32 | return; 33 | } 34 | ll cnt = 1, sum = 1; 35 | while(sum + cnt * k < n) 36 | { 37 | cnt *= k; 38 | sum += cnt; 39 | } 40 | ll l = (n - sum - 1) / cnt; 41 | ll r = k - 1 - l; 42 | if(l & 1) ans ^= sum; 43 | if(r & 1) ans ^= (sum - cnt); 44 | n -= (1 + l * sum + r * (sum - cnt)); 45 | ans ^= n; 46 | dfs(n, k); 47 | } 48 | int main() 49 | { 50 | ios::sync_with_stdio(false); 51 | int t; 52 | cin >> t; 53 | while(t --) 54 | { 55 | ll n, k; 56 | cin >> n >> k; 57 | if(k == 1) 58 | { 59 | cout << Xor(n) << endl; 60 | } 61 | else 62 | { 63 | ans = n; 64 | dfs(n, k); 65 | cout << ans << endl; 66 | } 67 | } 68 | return 0; 69 | } 70 | 71 | ``` -------------------------------------------------------------------------------- /HDU_6127 Hard challenge 【思维】.md: -------------------------------------------------------------------------------- 1 | HDU_6127 Hard challenge 【思维】 2 | 3 | 4 | > [题目链接](http://acm.hdu.edu.cn/showproblem.php?pid=6127) 5 | 6 | ### 题目描述 ### 7 | 平面上有(n)个点,已知每个点的坐标为((x,y)),以及改点的权值为(val),任意两点之间可以连接成一条不经过原点的边,该边的权值是两个端点的权值的乘积。现在要求画一条经过原点的直线,定义这条直线的权值为这条直线穿过的所有线段的值的和,问权值的最大值。 8 | ### 解题思路 ### 9 | 我们将所有的点按照极角排序,分别散落在y轴的左右两侧,y轴左端的点的和为suml,右端左右和为sumr,则全职和为suml*sumr 。我们先以y轴为起始点,不断偏移,更新suml,sumr。以便刷新ans,找到最大的权值。因为两点不经过原点,所以只能偏移一个点。 10 | ### 代码部分 ### 11 | ```cpp 12 | #include 13 | #include 14 | #include 15 | typedef long long ll; 16 | using namespace std; 17 | #define eps 1e-9 18 | #define maxn 200005 19 | struct Node 20 | { 21 | double x,y,ang;///横纵坐标,极角 22 | int v;///权值 23 | } p[maxn]; 24 | 25 | bool cmp(Node a,Node b)///结构体按照极角从小到大排序 26 | { 27 | return a.angeps)sumL+=p[i].v; 43 | else sumR+=p[i].v; 44 | ans=sumL*sumR; 45 | for(int i=1; i<=n; i++) 46 | { 47 | if (p[i].x>0)sumL-=p[i].v,sumR+=p[i].v; 48 | else sumL+=p[i].v,sumR-=p[i].v; 49 | ans=max(ans,sumL*sumR); 50 | } 51 | printf("%lld\n",ans); 52 | } 53 | int main() 54 | { 55 | scanf("%d",&T); 56 | while (T--) 57 | work(); 58 | return 0; 59 | } 60 | ``` -------------------------------------------------------------------------------- /HDU_6138 Fleet of the Eternal Throne 【AC自动机&&思维】【静态数组】.md: -------------------------------------------------------------------------------- 1 | HDU_6138 Fleet of the Eternal Throne 【AC自动机&&思维】【静态数组】 2 | 3 | 4 | > [题目链接](http://acm.hdu.edu.cn/showproblem.php?pid=6138) 5 | 6 | ### 题目描述 ### 7 | 给定n个字符,m个查询,每个查询包括两个数字,要求从下标为这两个字符串中找到一个最长公共子串,并且这个子串为这n个字符的前缀。 8 | ### 解题思路 ### 9 | AC自动机模板稍作修改。写这种题目各种绝望,链表写着写着发现不行,然后就创建静态链表,接着又是一阵GG,最后还是数组写的比较好,该题解写完命已呜呼。 10 | 步入正题,我们先根据给定的n个字符创建线段树,节点之中保存这该前缀的长度。然后就是常规的建立fail指针。 11 | 然后我们将要查询的第一个字符先跑一遍AC自动机,并在中留下标记,表明这是第一个字符串的子串并且为这n个字符的前缀,紧接着跑第二个字符串,如果遍历到某个节点,这个节点已经被第一个标记过了,那么就表明该节点就是这两个串的公共子串并且是这n个字符的某个前缀,现在的目标就是应该不断刷新找出最大值。 12 | ### 代码部分 ### 13 | ```cpp 14 | #include 15 | using namespace std; 16 | const int maxn = 1e5 + 10; 17 | const int en = 26; 18 | int nex[maxn][en];//用来构建字典树,表明下个节点 19 | int cnt[maxn]; // 用来存放该前缀后缀经过的次数。 20 | bool vis[maxn][2];//用来做标记 21 | int fail[maxn];//存放失败指针 22 | int id, root, ans; 23 | string A[maxn]; 24 | inline int idx(char c) {return c - 'a';} 25 | inline int CreatTrie() 26 | { 27 | for(int i = 0; i < en; ++ i) 28 | nex[id][i] = -1; 29 | fail[id] = -1; 30 | cnt[id] = 0; 31 | vis[id][0] = vis[id][1] = false; 32 | return id ++; 33 | } 34 | inline void Init() 35 | { 36 | id = 0; 37 | root = CreatTrie(); 38 | } 39 | void Insert(string c) 40 | { 41 | int len = c.size(); 42 | int p = root; 43 | for(int i = 0; i < len; ++ i) 44 | { 45 | int t = idx(c[i]); 46 | if(nex[p][t] == -1) nex[p][t] = CreatTrie(); 47 | cnt[nex[p][t]] = cnt[p] + 1; 48 | p = nex[p][t]; 49 | } 50 | } 51 | void Bfail() 52 | { 53 | int p = root; 54 | queue que; 55 | que.push(p); 56 | while(!que.empty()) 57 | { 58 | p = que.front(); 59 | que.pop(); 60 | for(int i = 0; i < en; ++ i) 61 | { 62 | if(nex[p][i] != -1) 63 | { 64 | if(p == 0) fail[nex[p][i]] = 0; 65 | else 66 | { 67 | int q = fail[p]; 68 | while(q != -1) 69 | { 70 | if(nex[q][i] != -1) 71 | { 72 | fail[nex[p][i]] = nex[q][i]; 73 | break; 74 | } 75 | q = fail[q]; 76 | } 77 | if(q == -1) fail[nex[p][i]] = 0; 78 | } 79 | que.push(nex[p][i]); 80 | } 81 | } 82 | } 83 | } 84 | void ACauto(string c, int cas) 85 | { 86 | ans = 0; 87 | int p = root; 88 | int len = c.size(); 89 | for(int i = 0; i < len; ++ i) 90 | { 91 | int t = idx(c[i]); 92 | while(nex[p][t] == -1 && p != 0) p = fail[p]; 93 | p = nex[p][t]; 94 | if(p == -1) p = 0; 95 | int q = p; 96 | while(q != 0 && vis[q][cas] == false) 97 | { 98 | vis[q][cas] = true; 99 | if(cas == 1) 100 | { 101 | if(vis[q][0] == true && vis[q][1] == true) ans = max(ans, cnt[q]); 102 | } 103 | q = fail[q]; 104 | } 105 | } 106 | } 107 | int main() 108 | { 109 | ios::sync_with_stdio(false); 110 | int t; 111 | cin >> t; 112 | while(t --) 113 | { 114 | Init(); 115 | int n, m, t1, t2; 116 | cin >> n; 117 | for(int i = 1; i <= n; ++ i) 118 | { 119 | cin >> A[i]; 120 | Insert(A[i]); 121 | } 122 | for(int i = 0; i < id; ++ i) 123 | cout << cnt[i] << endl; 124 | Bfail(); 125 | for(int i = 0; i < id; ++ i) 126 | { 127 | cout << cnt[i] << endl; 128 | cout << fail[i] << endl; 129 | } 130 | cin >> m; 131 | for(int i = 1; i <= m; ++ i) 132 | { 133 | memset(vis, false, sizeof(vis)); 134 | cin >> t1 >> t2; 135 | ACauto(A[t1], 0); 136 | ACauto(A[t2], 1); 137 | cout << ans << endl; 138 | } 139 | } 140 | return 0; 141 | } 142 | 143 | ``` -------------------------------------------------------------------------------- /HDU_6140 Hybrid Crystals 【思维】.md: -------------------------------------------------------------------------------- 1 | HDU_6140 Hybrid Crystals 【思维】 2 | 3 | 4 | > [题目链接](http://acm.hdu.edu.cn/showproblem.php?pid=6140) 5 | 6 | ### 题目描述 ### 7 | 搞得好像阅读理解,就是给定一个序列。序列的每个值都有属性,N代表可加可减,L代表只能加,D代表只能减,当然这些都可以用或者不用。给定一个k,问k是否可能用这个序列构成。 8 | ### 解题思路 ### 9 | 比赛的时候写了深搜,写了dp各种超。最后下来看别人题解,就是一开始固定一个左右区间L,R,不断的加数扩大区间范围,当最后k在L~R之内就输出"yes",否则输出"no"。测试一下真的过了,一脸懵逼,出题的的大哥,请收下小弟的膝盖。。。 10 | ### 代码部分 ### 11 | ```cpp 12 | #include 13 | using namespace std; 14 | const int maxn = 1e3 +10; 15 | struct node 16 | { 17 | int n; 18 | char c; 19 | } N[maxn]; 20 | int main() 21 | { 22 | int t; 23 | scanf("%d", &t); 24 | while(t --) 25 | { 26 | int m, k; 27 | int Left, Right; 28 | Left = Right = 0; 29 | scanf("%d %d",&m,&k); 30 | for(int i = 1; i<=m; i++) 31 | { 32 | scanf("%d",&N[i].n); 33 | } 34 | for(int i = 1; i<=m; i++) 35 | { 36 | scanf(" %c",&N[i].c); 37 | } 38 | for(int i = 1; i <= m; ++ i) 39 | { 40 | if(N[i].c == 'N')Left -= N[i].n, Right += N[i].n; 41 | else if(N[i].c == 'L') Right += N[i].n; 42 | else Left -= N[i].n; 43 | } 44 | 45 | if(k>=0) 46 | { 47 | if(k <= Right) 48 | printf("yes\n"); 49 | else 50 | printf("no\n"); 51 | } 52 | else 53 | { 54 | if(k >=Left) 55 | printf("yes\n"); 56 | else 57 | printf("no\n"); 58 | } 59 | } 60 | return 0; 61 | } 62 | 63 | ``` -------------------------------------------------------------------------------- /HDU_6152 Friend-Graph 【暴力】.md: -------------------------------------------------------------------------------- 1 | HDU_6152 Friend-Graph 【暴力】 2 | 3 | 4 | >[题目链接](http://acm.hdu.edu.cn/showproblem.php?pid=6152) 5 | 6 | ### 题目描述 ### 7 | 就是一个团队如果存在三个或者三个以上的人互为朋友的关系,或者都不是朋友的关系,那么就说明这个团队是一个坏团队,否则输出好团队。 8 | ### 解题思路 ### 9 | 暴力大法好,将每个人之间的关系都存于一个w数组之中,注意这里的数组为bool类型,暴力任意三个节点,如果他们之间的关系都为true或者false,则输出 "Bad Team!",否则输出"Great Team"。与此同时知道了能用bool类型的数组尽量用bool类型,因为bool类型所占内存较小。 10 | ### 代码部分 ### 11 | ```cpp 12 | #include 13 | #include 14 | #include 15 | using namespace std; 16 | const int maxn = 3e3; 17 | bool w[maxn][maxn]; 18 | 19 | bool flag(int n) 20 | { 21 | for(int i = 1; i <= n; ++ i) 22 | { 23 | for(int j = i + 1; j <= n; ++ j) 24 | { 25 | for(int k = j + 1; k <= n; ++ k) 26 | { 27 | if(w[i][j] == w[i][k] && w[i][k] == w[j][k] && w[i][j] == w[j][k]) 28 | return true; 29 | } 30 | } 31 | } 32 | return false; 33 | } 34 | int main() 35 | { 36 | int t, n, m; 37 | scanf("%d", &t); 38 | while(t --) 39 | { 40 | scanf("%d", &n); 41 | for(int i = 1; i < n; ++ i) 42 | { 43 | for(int j = 1 + i; j <= n; ++ j) 44 | { 45 | scanf("%d", &m); 46 | if(m == 1) 47 | w[j][i] = w[i][j] = false; 48 | else 49 | w[j][i] = w[i][j] = true; 50 | } 51 | } 52 | 53 | if(flag(n)) 54 | printf("Bad Team!\n"); 55 | else 56 | printf("Great Team!\n"); 57 | } 58 | return 0; 59 | } 60 | 61 | ``` 62 | -------------------------------------------------------------------------------- /HDU_6153 A Secret【扩展KMP】.md: -------------------------------------------------------------------------------- 1 | HDU_6153 A Secret【扩展KMP】 2 | 3 | 4 | > [题目链接](http://acm.hdu.edu.cn/showproblem.php?pid=6153) 5 | 6 | 7 | ### 题目描述 ### 8 | 给定两个串,求其中一个串s的每个后缀在另一个串t中出现的次数乘以其长度之和。 9 | ### 解题思路 ### 10 | 扩展KMP模板题,将s和t串都逆序以后就变成了求前缀的问题了,扩展KMP求处从i位置开始的最长公共前缀存于数组,最后通过将数组的值不为0的进行一个等差数列和的和就可以了。 11 | ### 代码部分 ### 12 | ```cpp 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | using namespace std; 19 | const int maxn = 1e6 + 10; 20 | const int mod = 1e9 + 7; 21 | typedef long long ll; 22 | int cnt[maxn]; 23 | /* 24 | 扩展KMP 25 | Next[i]: P[i..m-1] 与 P[0..m-1]的最长公共前缀 26 | ex[i]: T[i..n-1] 与 P[0..m-1]的最长公共前缀 27 | */ 28 | inline ll Add(ll n) 29 | { 30 | ll m=((n%mod)*((n+1)%mod)/2)%mod; 31 | return m; 32 | } 33 | 34 | char T[maxn],P[maxn]; 35 | int Next[maxn],ex[maxn]; 36 | 37 | void pre_exkmp(char P[]) 38 | { 39 | int m=strlen(P); 40 | Next[0]=m; 41 | int j=0,k=1; 42 | while(j+1> t; 86 | while(t --) 87 | { 88 | cin >> T >> P; 89 | int lenT = strlen(T); 90 | int lenP = strlen(P); 91 | reverse(T, T + lenT); 92 | reverse(P, P + lenP); 93 | pre_exkmp(P); 94 | memset(Next, 0,sizeof(Next)); 95 | memset(ex, 0, sizeof(ex)); 96 | exkmp(P, T); 97 | ll ans = 0; 98 | for(int i = 0; i < lenT; ++ i) 99 | { 100 | if(ex[i]) 101 | ans = (ans + Add(ex[i]) % mod) % mod; 102 | } 103 | cout << ans % mod << endl; 104 | } 105 | return 0; 106 | } 107 | ``` -------------------------------------------------------------------------------- /HDU_6154 CaoHaha's staff 【规律打表】.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | >[题目链接](http://acm.hdu.edu.cn/showproblem.php?pid=6154) 4 | 5 | ### 题目描述 ### 6 | 在一个二维坐标系中,想要得到一块为n的面积,问最少在坐标轴上面画多少笔画可以得到这样的面积,笔画可以是上、下、左、右、两种对角线共六种。 7 | ### 解题思路 ### 8 | 规律题,打表求出笔画为n时可以得到最大的面积,然后在找的过程周找到大于等于n的最小的下标即可。 9 | 核心是一个找边长为根号`√2`倍数的菱形(正方形)。然后从第一个菱形边长为`√2`开始一点一点扩展,即可找出规律。规律可参考代码部分。 10 | 补充一下找规律的步骤: 11 | 12 | ![](http://www.adreame.com/wp-content/uploads/2017/08/DB.png) 13 | 14 | 看上图黑色的正方形为第一个长为 `√2` 的菱形,也就是起始位置。 15 | 16 | - 所以我们先将DB[4] = 2,也就是边长为4的最大为面积为2 17 | - 紧接着画5条边构成的最大面积,也就是红色的那条线,面积比第4条边多了0.5,这时候我们赋值x1为0.5。由此可以推断DB[5] = DB[5 - 1] + x1。 18 | - 6条边:为蓝色的边,面积比4条边多了2.0,这时候我们赋值x2为2.0。由此可以推断 DB[6] = DB[5 - 2] + x2。 19 | - 7条边:为绿色的边,面积比6条边多了1.5,这时候我们知道之前的x1 = x1 + 1.0。由此推断DB[7] = DB[7 - 1] + x1; 20 | - 8条边:为橙色的边,面积比6条边多了4.0,这时候我们知道之前的x2 = x2 + 2.0。由此推断DB[8] = DB [8 - 2] + x2; 21 | - 再接着往下就是和上面的一样,每次加四条边就是一个循环。自己拿笔画一下就可以得出答案了。 22 | 23 | ### 代码部分 ### 24 | ```cpp 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | using namespace std; 31 | const int maxn = 1e5; 32 | double DB[maxn]; 33 | int main() 34 | { 35 | ios::sync_with_stdio(false); 36 | double x1 = 0.5, x2 = 2; 37 | DB[4] = 2; 38 | for(int i = 5; i <= maxn ; ++ i) 39 | { 40 | if(i % 4 == 1)DB[i] = DB[i - 1] + x1, x1 += 1.0; 41 | if(i % 4 == 2)DB[i] = DB[i - 2] + x2, x2 += 2.0; 42 | if(i % 4 == 3)DB[i] = DB[i - 1] + x1; 43 | if(i % 4 == 0) DB[i] = DB[i - 2] + x2; 44 | } 45 | int t; 46 | cin >> t; 47 | while(t --) 48 | { 49 | double n; 50 | cin >> n; 51 | for(int i = 4; i <= maxn; ++ i) 52 | { 53 | if(DB[i] >= n) 54 | { 55 | cout << i << endl; 56 | break; 57 | } 58 | } 59 | } 60 | return 0; 61 | } 62 | ``` 63 | -------------------------------------------------------------------------------- /HDU_6165 FFF at Valentine 【DFS】.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | > [题目链接](http://acm.hdu.edu.cn/showproblem.php?pid=6165) 4 | 5 | ### 题目描述 ### 6 | 这题目真的是让人头疼,意思就是给你一个有向图,问是否任意两点可以到达(正反都可以)。 7 | ### 解题思路 ### 8 | 深搜遍历出所有的点可以到达的点进行一个标记,如果一点可以到达另一点,则标记可以到达,最后遍历一个n^2,判断是否两点之间无一条通路,如果有就说明这两个情侣没法见面,输出Light my fire!,否则输出I love you my love and our love save us! 9 | ### 代码部分 ### 10 | ```cpp 11 | #include 12 | using namespace std; 13 | const int maxn = 1e3 + 10; 14 | typedef long long ll; 15 | vector G[maxn]; //尾插法建立链表 16 | bool vis[maxn][maxn]; 17 | bool bj[maxn]; 18 | 19 | void dfs(int s, int e) 20 | { 21 | bj[e] = true; 22 | for(int i = 0; i < (int)G[e].size(); ++ i) 23 | { 24 | int t = G[e][i]; 25 | if(bj[t] == false) 26 | { 27 | vis[s][t] = true; 28 | dfs(s, t); 29 | } 30 | } 31 | } 32 | int main() 33 | { 34 | ios::sync_with_stdio(false); 35 | int t; 36 | cin >> t; 37 | while(t --) 38 | { 39 | int n, m, u, v; 40 | cin >> n >> m; 41 | for(int i = 1; i <= n; ++ i) G[i].clear(); 42 | for(int i = 1; i <= m; ++ i) 43 | { 44 | cin >> u >> v; 45 | G[u].push_back(v); 46 | } 47 | memset(vis, false, sizeof(vis)); 48 | for(int i = 1; i <= n; ++ i) 49 | { 50 | memset(bj, false, sizeof(bj)); 51 | dfs(i, i); 52 | } 53 | bool flag = true; 54 | for(int i = 1; i <= n; ++ i) 55 | { 56 | for(int j = 1; j <= n; ++ j) 57 | { 58 | if(i != j) 59 | { 60 | if(vis[i][j] == false && vis[j][i] == false) 61 | { 62 | flag = false; 63 | break; 64 | } 65 | } 66 | } 67 | if(flag == false) 68 | break; 69 | } 70 | if(flag) puts("I love you my love and our love save us!"); 71 | else puts("Light my fire!"); 72 | } 73 | return 0; 74 | } 75 | 76 | ``` 77 | -------------------------------------------------------------------------------- /HDU_6170 Numbers 【MAP&&思维】.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | > [题目链接](http://acm.hdu.edu.cn/showproblem.php?pid=6168) 4 | 5 | ### 题目描述 ### 6 | 给入n个数,里面有a序列的数和b序列的数,b序列的数为a序列两两组合得到。最后从小到大输出a序列。 7 | ### 解题思路 ### 8 | 根据题目要求得到a序列的数目n (n + n * (n - 1) / 2) = m;) 9 | 然后将输入的每个数都存到map当中,找出输入的最小的两个肯定是a序列的前两个,然后根据得到的两个数可以排除一个b,让后让其在map中计数减一,迭代出map中最小的就是a序列的其中一个,然后接着排除b,最后得到n个数即可。 10 | ### 代码部分 ### 11 | ```cpp 12 | #include 13 | using namespace std; 14 | const int maxn = 1e4; 15 | const int inf = 1e9; 16 | typedef long long ll; 17 | ll a[maxn]; 18 | int main() 19 | { 20 | ll num, m; 21 | while(~scanf("%lld", &m)) 22 | { 23 | map M; 24 | ll min1 = inf, min2 = inf + 10; 25 | ll n = (sqrt(8 * m + 1) - 1) / 2; //解方程求出n的个数 26 | for(ll i = 0; i < m; ++ i) //求出第一小和第二小的数存放在min1,min2 27 | { 28 | scanf("%lld", &num); 29 | if(num < min1) 30 | { 31 | min1 = min1; 32 | min1 = num; 33 | } 34 | else if(num < min2) 35 | { 36 | min2 = num; 37 | } 38 | M[num] ++; 39 | } 40 | M[min1] --; 41 | M[min2] --; 42 | a[0] = min1; 43 | a[1] = min2; 44 | ll ans = 1; 45 | while(ans != n - 1) 46 | { 47 | for(ll i = 0; i < ans; ++ i) 48 | { 49 | M[a[i] + a[ans]] --; 50 | } 51 | map::iterator it = M.begin(); 52 | while(it != M.end()) 53 | { 54 | if(it -> second == ll(0)) 55 | { 56 | M.erase(it ++); 57 | } 58 | else 59 | { 60 | break; 61 | } 62 | } 63 | a[++ ans] = M.begin() -> first; 64 | M[a[ans]] --; 65 | } 66 | printf("%lld\n", n); 67 | printf("%lld", a[0]); 68 | for(int i = 1; i < n; ++ i) 69 | printf("% lld", a[i]); 70 | printf("\n"); 71 | } 72 | return 0; 73 | } 74 | 75 | ``` 76 | -------------------------------------------------------------------------------- /HDU_6170 Two strings 【DP】.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | > [题目链接](http://acm.hdu.edu.cn/showproblem.php?pid=6170) 4 | 5 | ### 题目描述 ### 6 | 给定一个文本串和一个匹配串,问匹配串是否可以构成文本串,如果可以构成就输出"yes",否则就输出"no"。 7 | 文本串和匹配串都由大小写构成,但是匹配串当中包含了特殊的字符:'.'可以代替任意一种的字符,'*'可以将上一个字符前面与他相同的删除,也可以无限将前面的字符向后拓展,也可以不做任何操作。 8 | ### 解题思路 ### 9 | DP思想,开一个二维的dp数组,存放匹配串和文本串分别为i,j时 是否满足匹配。如果满足,值为真,否则值为假,最后判断`dp[len2][len1]`是否为真即可。 10 | **DP的思想** 11 | 12 | - 当文本串和匹配串相同,或者匹配串为.则满足为真的条件但要注意这里的dp应该是`dp[i][j] = dp[i - 1][j - 1];`只有前面都满足这才能为真。 13 | - 当匹配串的字符为 * ,当匹配串为*的时候,可以进行向前删除也可以向后填充字符,满足其中一个那么`dp[i][j] = true;` 14 | - **特殊情况**第二个为*,*可以起到删除前面的字符的作用,所以`dp[i][0] = true;` 15 | 16 | ### 代码部分 ### 17 | ```cpp 18 | #include 19 | using namespace std; 20 | const int maxn = 1e4; 21 | bool dp[maxn >> 1][maxn >> 1]; //存放的是匹配串和文本串分别为i,j时 是否满足匹配 22 | char s1[maxn >> 1];//文本串 23 | char s2[maxn >> 1];//匹配串 24 | 25 | int main() 26 | { 27 | ios::sync_with_stdio(false); 28 | int t; 29 | cin >> t; 30 | while(t --) 31 | { 32 | cin >> s1 + 1; 33 | cin >> s2 + 1; 34 | memset(dp, false, sizeof(dp)); 35 | dp[0][0] = true; 36 | int len1 = strlen(s1 + 1); 37 | int len2 = strlen(s2 + 1); 38 | for(int i = 1; i <= len2; ++ i) 39 | { 40 | ///当匹配串第二个为*的时候就说明dp[i][0]都可以匹配,因为它可以把前面的字符给删掉 41 | if(i == 2 && s2[i] == '*') dp[i][0] = true; 42 | for(int j = 1; j <= len1; ++ j) 43 | { 44 | ///当匹配字符为.的时候或者文本串和匹配串相等的时候,dp[i][j]都等于前一个状态 45 | if(s2[i] == '.' || s1[j] == s2[i]) dp[i][j] = dp[i - 1][j - 1]; 46 | else 47 | { 48 | if(s2[i] == '*') 49 | { 50 | ///当匹配串为*的时候,可以进行向前删除也可以向后填充字符,满足其中一个那么dp[i][j] 都为真 51 | ///满足删除的条件dp[i][j]为真 52 | dp[i][j] = dp[i - 1][j] | dp[i - 2][j]; 53 | ///这个就是代表*可以像后面填充相同字符,然后dp[i][j]为真 54 | if((dp[i - 1][j - 1] || dp[i][j - 1]) && s1[j - 1] == s1[j]) dp[i][j] = true; 55 | } 56 | } 57 | } 58 | } 59 | puts(dp[len2][len1]?"yes":"no"); 60 | } 61 | return 0; 62 | } 63 | 64 | ``` 65 | -------------------------------------------------------------------------------- /HDU_【2017 Multi-University Training Contest 1】.md: -------------------------------------------------------------------------------- 1 | HDU_【2017 Multi-University Training Contest 1】 2 | 3 | 4 | ### 1001. Add More Zero ### 5 | 6 | > [题目链接](http://acm.hdu.edu.cn/showproblem.php?pid=6033) 7 | 8 | #### 题目意思: #### 9 | 没啥意思,单纯的数学公式。 10 | 求log10(2^m-1)向下取整 11 | 12 | ![](http://115.159.67.147/wp-content/uploads/2017/07/1.png) 13 | 14 | #### 代码部分: #### 15 | 16 | ```cpp 17 | #include 18 | #include 19 | #include 20 | 21 | using namespace std; 22 | 23 | int main() 24 | { 25 | int m,k,t=0; 26 | while(~scanf("%d",&m)) 27 | { 28 | k = (int)m*log10(2.0); 29 | printf("Case #%d: %d\n",++t,k); 30 | } 31 | return 0; 32 | } 33 | ``` 34 | ### 1011. KazaQ's Socks ### 35 | 36 | >[原题链接](http://acm.hdu.edu.cn/showproblem.php?pid=6043) 37 | 38 | #### 题目意思: #### 39 | 一个人有n个袜子,编号1-n,每天穿一双,穿得只剩下一只的时候,会洗好按顺序放过去,继续穿,问第k次穿得袜子是编号多少的袜子。 40 | #### 解题思路: #### 41 | 这是一道规律题。规律如下: 42 | 当n=3,袜子的编号:12312131213……由此可见`123`,`12`,`13`,`12`,`13`……12和13会一直循环下去 43 | 当n=4,袜子的编号:1234123124123124……由此可见`1234`,`123`,`124`,`123`,`124`……123和124会一直循环下去 44 | 等等 45 | 具体实现看代码部分吧 46 | #### 代码部分: #### 47 | 48 | ```cpp 49 | #include 50 | #include 51 | #define LL long long int 52 | using namespace std; 53 | 54 | int main() 55 | { 56 | LL n,m; 57 | int flag=1; 58 | while(~scanf("%lld%lld",&m,&n)) 59 | { 60 | printf("Case #%d: ",flag++); 61 | if(n <= m) 62 | { 63 | printf("%lld\n",n); 64 | } 65 | else 66 | { 67 | n -= m; 68 | LL a,b; 69 | a = n/(m-1); 70 | b = n%(m-1); 71 | if(b == 0) 72 | { 73 | if(a%2==0) 74 | { 75 | printf("%lld\n",m); 76 | } 77 | else 78 | { 79 | printf("%lld\n",m-1); 80 | } 81 | } 82 | else 83 | { 84 | printf("%lld\n",b); 85 | } 86 | } 87 | } 88 | return 0; 89 | } 90 | ``` 91 | 92 | **总结下这次比赛,干坐五个小时,就做出来两道签到题。看大佬AC,叹息12与2的差距。不说了,补题……** -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # hdu-code 2 | 杭电oj的题目 3 | -------------------------------------------------------------------------------- /杭电多校联赛2017年总结.md: -------------------------------------------------------------------------------- 1 | 杭电多校联赛2017年总结 2 | 3 | ### 杭电多校联赛2017年总结 ### 4 |   时间确实过得挺快,一个月很快结束,说实在的,一个月了,每次比赛都是混两道签到题,然后就开始挂机了,确实很不甘心,看着联赛的排名,确实感觉,和强校之前的差距还是很大的,PS:论12与2的差距。 5 |   虽然每次都是签到两道题,挂机五小时,但不得不承认,多校联赛给带来的收益也是很大的,学到了很多以前都不知道的东西(弱校蒟蒻能力有待提高)。 6 |   通过这十场比赛,可以明显感觉到,现在的ACM比赛会更加考验人的思维能力,数学能力,当然好的算法基础必不可少。通过比赛,更加认清自己与别人的差距,自己的算法知识的不足。也通过比赛,更加增加了我们队伍之间的默契,也期待我们的队伍在今后可以取得更好的成绩。 7 |   多校联赛已经结束了,博文也更新了大概70篇,学习了字典树、AC自动机、数论的一些知识等,要学的东西还很多,紧接着就是九月份众多的区域赛网络竞选赛,全力以赴,不留遗憾,我可是要下年省赛夺金的人,哈哈哈哈~ 8 | 9 | ### 给自己、队友、小宝、朋友送上几句激励的话 ### 10 | 11 | >别在你最该努力的时候,选择了安逸。 12 | >抱有最大的希望,做出最大的努力。 13 | >如果你想攀登高峰,切莫把彩虹当做梯子。 14 | --------------------------------------------------------------------------------